mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-13 04:53:01 +00:00
Delay instantiation of User and Spawner objects
Avoids instantiating too many objects before they are used - deletes Spawner instances after they stop to avoid lingering instances - use user_dict cache more often instead of db queries - check for empty spawners dict to avoid a few Spawner instantiations
This commit is contained in:
@@ -104,7 +104,10 @@ class APIHandler(BaseHandler):
|
|||||||
'pending': None,
|
'pending': None,
|
||||||
'last_activity': user.last_activity.isoformat(),
|
'last_activity': user.last_activity.isoformat(),
|
||||||
}
|
}
|
||||||
|
if '' in user.spawners:
|
||||||
model['pending'] = user.spawners[''].pending or None
|
model['pending'] = user.spawners[''].pending or None
|
||||||
|
else:
|
||||||
|
model['pending'] = False
|
||||||
|
|
||||||
if self.allow_named_servers:
|
if self.allow_named_servers:
|
||||||
servers = model['servers'] = {}
|
servers = model['servers'] = {}
|
||||||
|
@@ -81,7 +81,7 @@ class UserListAPIHandler(APIHandler):
|
|||||||
yield gen.maybe_future(self.authenticator.add_user(user))
|
yield gen.maybe_future(self.authenticator.add_user(user))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error("Failed to create user: %s" % name, exc_info=True)
|
self.log.error("Failed to create user: %s" % name, exc_info=True)
|
||||||
del self.users[user]
|
self.users.delete(user)
|
||||||
raise web.HTTPError(400, "Failed to create user %s: %s" % (name, str(e)))
|
raise web.HTTPError(400, "Failed to create user %s: %s" % (name, str(e)))
|
||||||
else:
|
else:
|
||||||
created.append(user)
|
created.append(user)
|
||||||
@@ -132,7 +132,7 @@ class UserAPIHandler(APIHandler):
|
|||||||
except Exception:
|
except Exception:
|
||||||
self.log.error("Failed to create user: %s" % name, exc_info=True)
|
self.log.error("Failed to create user: %s" % name, exc_info=True)
|
||||||
# remove from registry
|
# remove from registry
|
||||||
del self.users[user]
|
self.users.delete(user)
|
||||||
raise web.HTTPError(400, "Failed to create user: %s" % name)
|
raise web.HTTPError(400, "Failed to create user: %s" % name)
|
||||||
|
|
||||||
self.write(json.dumps(self.user_model(user)))
|
self.write(json.dumps(self.user_model(user)))
|
||||||
@@ -155,7 +155,7 @@ class UserAPIHandler(APIHandler):
|
|||||||
|
|
||||||
yield gen.maybe_future(self.authenticator.delete_user(user))
|
yield gen.maybe_future(self.authenticator.delete_user(user))
|
||||||
# remove from registry
|
# remove from registry
|
||||||
del self.users[user]
|
self.users.delete(user)
|
||||||
|
|
||||||
self.set_status(204)
|
self.set_status(204)
|
||||||
|
|
||||||
|
@@ -1034,6 +1034,8 @@ class JupyterHub(Application):
|
|||||||
without notifying JupyterHub.
|
without notifying JupyterHub.
|
||||||
"""))
|
"""))
|
||||||
db.commit()
|
db.commit()
|
||||||
|
# expunge User objects from the db session
|
||||||
|
db.expunge_all()
|
||||||
|
|
||||||
# The whitelist set and the users in the db are now the same.
|
# The whitelist set and the users in the db are now the same.
|
||||||
# From this point on, any user changes should be done simultaneously
|
# From this point on, any user changes should be done simultaneously
|
||||||
@@ -1060,6 +1062,8 @@ class JupyterHub(Application):
|
|||||||
db.add(user)
|
db.add(user)
|
||||||
group.users.append(user)
|
group.users.append(user)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
# expunge Group objects from the db session
|
||||||
|
db.expunge_all()
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def _add_tokens(self, token_dict, kind):
|
def _add_tokens(self, token_dict, kind):
|
||||||
@@ -1263,7 +1267,7 @@ class JupyterHub(Application):
|
|||||||
continue
|
continue
|
||||||
seen.add(orm_spawner.user_id)
|
seen.add(orm_spawner.user_id)
|
||||||
orm_user = orm_spawner.user
|
orm_user = orm_spawner.user
|
||||||
self.users[orm_user.id] = user = User(orm_user, self.tornado_settings)
|
user = self.users.add(orm_user)
|
||||||
self.log.debug("Loading state for %s from db", user.name)
|
self.log.debug("Loading state for %s from db", user.name)
|
||||||
for name, spawner in user.spawners.items():
|
for name, spawner in user.spawners.items():
|
||||||
f = check_spawner(user, name, spawner)
|
f = check_spawner(user, name, spawner)
|
||||||
@@ -1271,6 +1275,15 @@ class JupyterHub(Application):
|
|||||||
|
|
||||||
# await checks after submitting them all
|
# await checks after submitting them all
|
||||||
yield gen.multi(check_futures)
|
yield gen.multi(check_futures)
|
||||||
|
to_expunge = []
|
||||||
|
for user in self.users.values():
|
||||||
|
if not user.active:
|
||||||
|
to_expunge.append(user)
|
||||||
|
if to_expunge:
|
||||||
|
self.log.debug("expunging users from db session: %s",
|
||||||
|
', '.join(u.name for u in to_expunge))
|
||||||
|
for user in to_expunge:
|
||||||
|
del self.users[user.id]
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
# only perform this query if we are going to log it
|
# only perform this query if we are going to log it
|
||||||
|
@@ -258,7 +258,6 @@ class BaseHandler(RequestHandler):
|
|||||||
self.db.add(u)
|
self.db.add(u)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
user = self._user_from_orm(u)
|
user = self._user_from_orm(u)
|
||||||
self.authenticator.add_user(user)
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def clear_login_cookie(self, name=None):
|
def clear_login_cookie(self, name=None):
|
||||||
|
@@ -262,13 +262,11 @@ class Proxy(LoggingConfigurable):
|
|||||||
"""
|
"""
|
||||||
db = self.db
|
db = self.db
|
||||||
futures = []
|
futures = []
|
||||||
for orm_service in db.query(Service):
|
for service in service_dict.values():
|
||||||
service = service_dict[orm_service.name]
|
|
||||||
if service.server:
|
if service.server:
|
||||||
futures.append(self.add_service(service))
|
futures.append(self.add_service(service))
|
||||||
# wait after submitting them all
|
# wait after submitting them all
|
||||||
for f in futures:
|
yield gen.multi(futures)
|
||||||
yield f
|
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def add_all_users(self, user_dict):
|
def add_all_users(self, user_dict):
|
||||||
@@ -278,14 +276,12 @@ class Proxy(LoggingConfigurable):
|
|||||||
"""
|
"""
|
||||||
db = self.db
|
db = self.db
|
||||||
futures = []
|
futures = []
|
||||||
for orm_user in db.query(User):
|
for user in user_dict.values():
|
||||||
user = user_dict[orm_user]
|
|
||||||
for name, spawner in user.spawners.items():
|
for name, spawner in user.spawners.items():
|
||||||
if spawner.ready:
|
if spawner.ready:
|
||||||
futures.append(self.add_user(user, name))
|
futures.append(self.add_user(user, name))
|
||||||
# wait after submitting them all
|
# wait after submitting them all
|
||||||
for f in futures:
|
yield gen.multi(futures)
|
||||||
yield f
|
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def check_routes(self, user_dict, service_dict, routes=None):
|
def check_routes(self, user_dict, service_dict, routes=None):
|
||||||
@@ -309,8 +305,7 @@ class Proxy(LoggingConfigurable):
|
|||||||
self.log.warning("Updating default route %s → %s", route['target'], hub.host)
|
self.log.warning("Updating default route %s → %s", route['target'], hub.host)
|
||||||
futures.append(self.add_hub_route(hub))
|
futures.append(self.add_hub_route(hub))
|
||||||
|
|
||||||
for orm_user in db.query(User):
|
for user in user_dict.values():
|
||||||
user = user_dict[orm_user]
|
|
||||||
for name, spawner in user.spawners.items():
|
for name, spawner in user.spawners.items():
|
||||||
if spawner.ready:
|
if spawner.ready:
|
||||||
spec = spawner.proxy_spec
|
spec = spawner.proxy_spec
|
||||||
@@ -333,14 +328,8 @@ class Proxy(LoggingConfigurable):
|
|||||||
# check service routes
|
# check service routes
|
||||||
service_routes = {r['data']['service']: r
|
service_routes = {r['data']['service']: r
|
||||||
for r in routes.values() if 'service' in r['data']}
|
for r in routes.values() if 'service' in r['data']}
|
||||||
for orm_service in db.query(Service).filter(Service.server != None):
|
for service in service_dict.values():
|
||||||
service = service_dict[orm_service.name]
|
|
||||||
if service.server is None:
|
if service.server is None:
|
||||||
# This should never be True, but seems to be on rare occasion.
|
|
||||||
# catch filter bug, either in sqlalchemy or my understanding of
|
|
||||||
# its behavior
|
|
||||||
self.log.error(
|
|
||||||
"Service %s has no server, but wasn't filtered out.", service)
|
|
||||||
continue
|
continue
|
||||||
good_routes.add(service.proxy_spec)
|
good_routes.add(service.proxy_spec)
|
||||||
if service.name not in service_routes:
|
if service.name not in service_routes:
|
||||||
|
@@ -34,6 +34,15 @@ class UserDict(dict):
|
|||||||
def db(self):
|
def db(self):
|
||||||
return self.db_factory()
|
return self.db_factory()
|
||||||
|
|
||||||
|
def from_orm(self, orm_user):
|
||||||
|
return User(orm_user, self.settings)
|
||||||
|
|
||||||
|
def add(self, orm_user):
|
||||||
|
"""Add a user to the UserDict"""
|
||||||
|
if orm_user.id not in self:
|
||||||
|
self[orm_user.id] = self.from_orm(orm_user)
|
||||||
|
return self[orm_user.id]
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
if isinstance(key, (User, orm.User)):
|
if isinstance(key, (User, orm.User)):
|
||||||
key = key.id
|
key = key.id
|
||||||
@@ -63,18 +72,30 @@ class UserDict(dict):
|
|||||||
orm_user = self.db.query(orm.User).filter(orm.User.id == id).first()
|
orm_user = self.db.query(orm.User).filter(orm.User.id == id).first()
|
||||||
if orm_user is None:
|
if orm_user is None:
|
||||||
raise KeyError("No such user: %s" % id)
|
raise KeyError("No such user: %s" % id)
|
||||||
user = self[id] = User(orm_user, self.settings)
|
user = self.add(orm_user)
|
||||||
return dict.__getitem__(self, id)
|
else:
|
||||||
|
user = dict.__getitem__(self, id)
|
||||||
|
return user
|
||||||
else:
|
else:
|
||||||
raise KeyError(repr(key))
|
raise KeyError(repr(key))
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
|
user = self[key]
|
||||||
|
for orm_spawner in user.orm_user._orm_spawners:
|
||||||
|
if orm_spawner in self.db:
|
||||||
|
self.db.expunge(orm_spawner)
|
||||||
|
if user.orm_user in self.db:
|
||||||
|
self.db.expunge(user.orm_user)
|
||||||
|
dict.__delitem__(self, user.id)
|
||||||
|
|
||||||
|
def delete(self, key):
|
||||||
|
"""Delete a user from the cache and the database"""
|
||||||
user = self[key]
|
user = self[key]
|
||||||
user_id = user.id
|
user_id = user.id
|
||||||
db = self.db
|
self.db.delete(user)
|
||||||
db.delete(user.orm_user)
|
self.db.commit()
|
||||||
db.commit()
|
# delete from dict after commit
|
||||||
dict.__delitem__(self, user_id)
|
del self[user_id]
|
||||||
|
|
||||||
def count_active_users(self):
|
def count_active_users(self):
|
||||||
"""Count the number of user servers that are active/pending/ready
|
"""Count the number of user servers that are active/pending/ready
|
||||||
@@ -237,11 +258,15 @@ class User(HasTraits):
|
|||||||
@property
|
@property
|
||||||
def running(self):
|
def running(self):
|
||||||
"""property for whether the user's default server is running"""
|
"""property for whether the user's default server is running"""
|
||||||
|
if not self.spawners:
|
||||||
|
return False
|
||||||
return self.spawner.ready
|
return self.spawner.ready
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def active(self):
|
def active(self):
|
||||||
"""True if any server is active"""
|
"""True if any server is active"""
|
||||||
|
if not self.spawners:
|
||||||
|
return False
|
||||||
return any(s.active for s in self.spawners.values())
|
return any(s.active for s in self.spawners.values())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -371,7 +396,7 @@ class User(HasTraits):
|
|||||||
# wait for spawner.start to return
|
# wait for spawner.start to return
|
||||||
try:
|
try:
|
||||||
# run optional preparation work to bootstrap the notebook
|
# run optional preparation work to bootstrap the notebook
|
||||||
yield gen.maybe_future(self.spawner.run_pre_spawn_hook())
|
yield gen.maybe_future(spawner.run_pre_spawn_hook())
|
||||||
f = spawner.start()
|
f = spawner.start()
|
||||||
# commit any changes in spawner.start (always commit db changes before yield)
|
# commit any changes in spawner.start (always commit db changes before yield)
|
||||||
db.commit()
|
db.commit()
|
||||||
@@ -524,3 +549,5 @@ class User(HasTraits):
|
|||||||
except Exception:
|
except Exception:
|
||||||
self.log.exception("Error in Authenticator.post_spawn_stop for %s", self)
|
self.log.exception("Error in Authenticator.post_spawn_stop for %s", self)
|
||||||
spawner._stop_pending = False
|
spawner._stop_pending = False
|
||||||
|
# pop the Spawner object
|
||||||
|
self.spawners.pop(server_name)
|
||||||
|
Reference in New Issue
Block a user