diff --git a/jupyterhub/app.py b/jupyterhub/app.py index 41abcf4c..22e7bb53 100644 --- a/jupyterhub/app.py +++ b/jupyterhub/app.py @@ -2013,12 +2013,19 @@ class JupyterHub(Application): # parallelize checks for running Spawners # run query on extant Server objects # so this is O(running servers) not O(total users) + # Server objects can be associated with either a Spawner or a Service, + # we are only interested in the ones associated with a Spawner check_futures = [] for orm_server in db.query(orm.Server): - orm_spawners = orm_server.spawner - if not orm_spawners: + orm_spawner = orm_server.spawner + if not orm_spawner: + # sanity check for orphaned Server rows + # this shouldn't happen if we've got our sqlachemy right + if not orm_server.service: + self.log.warning("deleting orphaned server %s", orm_server) + self.db.delete(orm_server) + self.db.commit() continue - orm_spawner = orm_spawners[0] # instantiate Spawner wrapper and check if it's still alive # spawner should be running user = self.users[orm_spawner.user] diff --git a/jupyterhub/orm.py b/jupyterhub/orm.py index 4e151092..81c01dd3 100644 --- a/jupyterhub/orm.py +++ b/jupyterhub/orm.py @@ -26,6 +26,7 @@ from sqlalchemy import select from sqlalchemy import Table from sqlalchemy import Unicode from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import backref from sqlalchemy.orm import interfaces from sqlalchemy.orm import object_session from sqlalchemy.orm import relationship @@ -230,7 +231,12 @@ class Spawner(Base): user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE')) server_id = Column(Integer, ForeignKey('servers.id', ondelete='SET NULL')) - server = relationship(Server, backref='spawner', cascade="all") + server = relationship( + Server, + backref=backref('spawner', uselist=False), + single_parent=True, + cascade="all, delete-orphan", + ) state = Column(JSONDict) name = Column(Unicode(255)) @@ -282,7 +288,12 @@ class Service(Base): # service-specific interface _server_id = Column(Integer, ForeignKey('servers.id', ondelete='SET NULL')) - server = relationship(Server, backref='service', cascade='all') + server = relationship( + Server, + backref=backref('service', uselist=False), + single_parent=True, + cascade="all, delete-orphan", + ) pid = Column(Integer) def new_api_token(self, token=None, **kwargs):