Skip to content

Commit f2781f0

Browse files
committed
Run git update in the background
Instead of doing so while handling the webhook, as that causes GitHub to timeout on the big matplotlib.github.com repo waiting for us.
1 parent cf15581 commit f2781f0

File tree

2 files changed

+44
-23
lines changed

2 files changed

+44
-23
lines changed

test_webhook.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,6 @@ async def test_signature(monkeypatch):
3535
'unused')
3636

3737

38-
async def test_update_repo_error():
39-
"""Test that updating a repository fails as expected."""
40-
with pytest.raises(web.HTTPServerError,
41-
match='non-existent does not exist'):
42-
await update_repo(Path('non-existent'), 'unused',
43-
'matplotlib/non-existent')
44-
45-
4638
async def test_update_repo_empty(tmp_path_factory):
4739
"""Test that updating an empty repository works as expected."""
4840
repo1 = tmp_path_factory.mktemp('repo1')
@@ -137,15 +129,25 @@ async def test_github_webhook_errors(aiohttp_client, monkeypatch):
137129
assert resp.status == 400
138130
assert 'incorrect repository' in await resp.text()
139131

132+
# Problem on our side.
133+
resp = await client.post(
134+
'/gh/non-existent',
135+
headers={**valid_headers, 'X-GitHub-Event': 'push'},
136+
data='{"sender": {"login": "QuLogic"}, "ref": "refs/heads/gh-pages", '
137+
'"repository": {"name": "non-existent", '
138+
'"owner": {"login": "matplotlib"}}}')
139+
assert resp.status == 500
140+
assert 'non-existent does not exist' in await resp.text()
141+
140142

141-
async def test_github_webhook_valid(aiohttp_client, monkeypatch):
143+
async def test_github_webhook_valid(aiohttp_client, monkeypatch, tmp_path):
142144
"""Test valid input to webhook."""
143145
client = await aiohttp_client(create_app())
144146

145147
# Do no actual work, since that's tested above.
146148
monkeypatch.setattr(webhook, 'verify_signature',
147149
mock.Mock(verify_signature, return_value=True))
148-
monkeypatch.setenv('SITE_DIR', 'non-existent-site-dir')
150+
monkeypatch.setenv('SITE_DIR', str(tmp_path))
149151
ur_mock = mock.Mock(update_repo, return_value=None)
150152
monkeypatch.setattr(webhook, 'update_repo', ur_mock)
151153

@@ -177,6 +179,8 @@ async def test_github_webhook_valid(aiohttp_client, monkeypatch):
177179
ur_mock.assert_not_called()
178180

179181
# Push event to gh-pages branch should run an update.
182+
tmp_repo = tmp_path / 'non-existent-repo'
183+
(tmp_repo / '.git').mkdir(parents=True, exist_ok=True)
180184
resp = await client.post(
181185
'/gh/non-existent-repo',
182186
headers={**valid_headers, 'X-GitHub-Event': 'push'},
@@ -186,5 +190,4 @@ async def test_github_webhook_valid(aiohttp_client, monkeypatch):
186190
' "owner": {"login": "matplotlib"}}}')
187191
assert resp.status == 200
188192
ur_mock.assert_called_once_with(
189-
Path('non-existent-site-dir/non-existent-repo'), 'foo',
190-
'matplotlib/non-existent-repo')
193+
tmp_repo, 'foo', 'matplotlib/non-existent-repo')

webhook.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,22 @@
3131

3232
async def update_repo(repo: Path, delivery: str, name: str):
3333
"""Update a git repo at the given path."""
34-
if not (repo / '.git').is_dir():
35-
raise web.HTTPServerError(
36-
reason=f'{delivery}: Checkout for {name} does not exist')
37-
3834
log.info('%s: Running git pull for %s at %s', delivery, name, repo)
3935
proc = await asyncio.create_subprocess_exec('git', 'pull', cwd=repo)
36+
await proc.wait()
37+
if proc.returncode != 0:
38+
log.error('%s: Running git pull for %s at %s failed: %d',
39+
delivery, name, repo, proc.returncode)
40+
41+
42+
def handle_update_repo_result(task: asyncio.Task):
43+
"""Clean up the git repo update tasks, logging an error if necessary."""
4044
try:
41-
await asyncio.wait_for(proc.wait(), timeout=60)
42-
except asyncio.TimeoutError:
43-
proc.kill()
44-
raise web.HTTPServerError(
45-
reason=f'{delivery}: Timed out updating git checkout of {name}')
45+
task.result()
46+
except asyncio.CancelledError:
47+
pass
48+
except Exception:
49+
log.exception('Exception raised by task %r', task)
4650

4751

4852
async def verify_signature(data: bytes, signature: bytes, repo: str,
@@ -137,7 +141,14 @@ async def github_webhook(request: web.Request):
137141
return web.Response(status=200)
138142

139143
checkout = Path(os.environ.get('SITE_DIR', 'sites'), repository)
140-
await update_repo(checkout, delivery, f'{organization}/{repository}')
144+
if not (checkout / '.git').is_dir():
145+
raise web.HTTPInternalServerError(
146+
reason=(f'{delivery}: Checkout for {organization}/{repository} '
147+
'does not exist'))
148+
task = asyncio.create_task(
149+
update_repo(checkout, delivery, f'{organization}/{repository}'),
150+
name=f'update_repo {repository}')
151+
task.add_done_callback(handle_update_repo_result)
141152

142153
return web.Response(status=200)
143154

@@ -166,4 +177,11 @@ def create_app():
166177
host = 'localhost'
167178
port = 8080
168179
app = create_app()
169-
web.run_app(app, host=host, port=port)
180+
try:
181+
web.run_app(app, host=host, port=port)
182+
except KeyboardInterrupt:
183+
# Finish up git update tasks.
184+
git_update_tasks = {task for task in asyncio.all_tasks()
185+
if task.get_name().startswith('update_repo')}
186+
asyncio.run_until_complete(
187+
asyncio.gather(*git_update_tasks, return_exceptions=True))

0 commit comments

Comments
 (0)