exercise start/stop race conditions

this doesn’t cover all the edge cases of each possible stage for the races, but it gets the basics covered.
This commit is contained in:
Min RK
2017-08-26 11:57:05 -04:00
parent 37da47d811
commit 8e3553462c
3 changed files with 49 additions and 2 deletions

View File

@@ -231,7 +231,10 @@ class UserServerAPIHandler(APIHandler):
return return
if not spawner.ready: if not spawner.ready:
raise web.HTTPError(400, "%s is not running" % spawner._log_name) raise web.HTTPError(
400, "%s is not running %s" %
(spawner._log_name, '(pending: %s)' % spawner.pending if spawner.pending else '')
)
# include notify, so that a server that died is noticed immediately # include notify, so that a server that died is noticed immediately
status = yield spawner.poll_and_notify() status = yield spawner.poll_and_notify()
if status is not None: if status is not None:

View File

@@ -514,7 +514,7 @@ class BaseHandler(RequestHandler):
raise KeyError("User %s has no such spawner %r", user.name, name) raise KeyError("User %s has no such spawner %r", user.name, name)
spawner = user.spawners[name] spawner = user.spawners[name]
if spawner.pending: if spawner.pending:
raise RuntimeError("%s pending %s" % (user_server_name, spawner.pending)) raise RuntimeError("%s pending %s" % (spawner._log_name, spawner.pending))
# set user._stop_pending before doing anything async # set user._stop_pending before doing anything async
# to avoid races # to avoid races
spawner._stop_pending = True spawner._stop_pending = True

View File

@@ -654,6 +654,50 @@ def test_active_server_limit(app, request):
assert counts['pending'] == 0 assert counts['pending'] == 0
@mark.gen_test
def test_start_stop_race(app, no_patience, slow_spawn):
user = add_user(app.db, app, name='panda')
spawner = user.spawner
# start the server
r = yield api_request(app, 'users', user.name, 'server', method='post')
assert r.status_code == 202
assert spawner.pending == 'spawn'
# additional spawns while spawning shouldn't trigger a new spawn
with mock.patch.object(spawner, 'start') as m:
r = yield api_request(app, 'users', user.name, 'server', method='post')
assert r.status_code == 202
assert m.call_count == 0
# stop while spawning is not okay
r = yield api_request(app, 'users', user.name, 'server', method='delete')
assert r.status_code == 400
while not spawner.ready:
yield gen.sleep(0.1)
spawner.delay = 3
# stop the spawner
r = yield api_request(app, 'users', user.name, 'server', method='delete')
assert r.status_code == 202
assert spawner.pending == 'stop'
# make sure we get past deleting from the proxy
yield gen.sleep(1)
# additional stops while stopping shouldn't trigger a new stop
with mock.patch.object(spawner, 'stop') as m:
r = yield api_request(app, 'users', user.name, 'server', method='delete')
assert r.status_code == 202
assert m.call_count == 0
# start while stopping is not allowed
with mock.patch.object(spawner, 'start') as m:
r = yield api_request(app, 'users', user.name, 'server', method='post')
assert r.status_code == 400
while spawner.active:
yield gen.sleep(0.1)
# start after stop is okay
r = yield api_request(app, 'users', user.name, 'server', method='post')
assert r.status_code == 202
@mark.gen_test @mark.gen_test
def test_get_proxy(app): def test_get_proxy(app):
r = yield api_request(app, 'proxy') r = yield api_request(app, 'proxy')