Beispiel #1
0
def _session(request):
    engine = request.registry['sqlalchemy.engine']
    session = Session(bind=engine)

    # If the request has a transaction manager, associate the session with it.
    try:
        tm = request.tm
    except AttributeError:
        pass
    else:
        zope.sqlalchemy.register(session, transaction_manager=tm)

    # Track uncommitted changes so we can verify that everything was either
    # committed or rolled back when the request finishes.
    db_session_checks = request.registry.settings.get('h.db_session_checks', True)
    if db_session_checks:
        tracker = Tracker(session)
    else:
        tracker = None

    # pyramid_tm doesn't always close the database session for us.
    #
    # For example if an exception view accesses the session and causes a new
    # transaction to be opened, pyramid_tm won't close this connection because
    # pyramid_tm's transaction has already ended before exception views are
    # executed.
    # Connections opened by NewResponse and finished callbacks aren't closed by
    # pyramid_tm either.
    #
    # So add our own callback here to make sure db sessions are always closed.
    #
    # See: https://github.com/Pylons/pyramid_tm/issues/40
    @request.add_finished_callback
    def close_the_sqlalchemy_session(request):
        changes = tracker.uncommitted_changes() if tracker else []
        if changes:
            msg = 'closing a session with uncommitted changes %s'
            log.warn(msg, changes, extra={
                'stack': True,
                'changes': changes,
            })
        session.close()

        # zope.sqlalchemy maintains an internal `id(session) => state` map with
        # an entry for each active DB session which is registered with it.
        #
        # Entries are normally cleared at the end of a request when the
        # transaction manager (`request.tm`) commits. DB writes after this can
        # leave stale entries in the map which can cause problems in future
        # requests if another session gets the same ID as the current one.
        dm = zope.sqlalchemy.datamanager
        if len(dm._SESSION_STATE) > 0:
            log.warn('request ended with non-empty zope.sqlalchemy state', extra={
                'data': {
                    'zope.sqlalchemy.datamanager._SESSION_STATE': dm._SESSION_STATE,
                },
            })
            dm._SESSION_STATE = {}

    return session
Beispiel #2
0
def _session(request):
    engine = request.registry["sqlalchemy.engine"]
    session = Session(bind=engine)

    # If the request has a transaction manager, associate the session with it.
    try:
        tm = request.tm
    except AttributeError:
        pass
    else:
        zope.sqlalchemy.register(session, transaction_manager=tm)

    # Track uncommitted changes so we can verify that everything was either
    # committed or rolled back when the request finishes.
    db_session_checks = request.registry.settings.get("h.db_session_checks",
                                                      True)
    if db_session_checks:
        tracker = Tracker(session)
    else:
        tracker = None

    # pyramid_tm doesn't always close the database session for us.
    #
    # For example if an exception view accesses the session and causes a new
    # transaction to be opened, pyramid_tm won't close this connection because
    # pyramid_tm's transaction has already ended before exception views are
    # executed.
    # Connections opened by NewResponse and finished callbacks aren't closed by
    # pyramid_tm either.
    #
    # So add our own callback here to make sure db sessions are always closed.
    #
    # See: https://github.com/Pylons/pyramid_tm/issues/40
    @request.add_finished_callback
    def close_the_sqlalchemy_session(request):
        if len(session.transaction._connections) > 1:
            # There appear to still be open DB connections belonging to this
            # request. This shouldn't happen.
            changes = tracker.uncommitted_changes() if tracker else []
            if changes:
                msg = "closing a session with uncommitted changes %s"
                log.warning(msg,
                            changes,
                            extra={
                                "stack": True,
                                "changes": changes
                            })
            else:
                log.warning(
                    "closing an unclosed DB session (no uncommitted changes)",
                    extra={"stack": True},
                )
        # Close any unclosed DB connections.
        # This is done outside of the `if` statement above just in case: it's
        # okay to call `session.close()` even if the session does not need to
        # be closed, so just call it unconditionally so that there's no chance
        # of leaking any unclosed DB connections.
        session.close()

    return session
def _session(request):
    engine = request.registry["sqlalchemy.engine"]
    session = Session(bind=engine)

    # If the request has a transaction manager, associate the session with it.
    try:
        tm = request.tm
    except AttributeError:
        pass
    else:
        zope.sqlalchemy.register(session, transaction_manager=tm)

    # Track uncommitted changes so we can verify that everything was either
    # committed or rolled back when the request finishes.
    db_session_checks = request.registry.settings.get("h.db_session_checks",
                                                      True)
    if db_session_checks:
        tracker = Tracker(session)
    else:
        tracker = None

    # pyramid_tm doesn't always close the database session for us.
    #
    # If anything that executes later in the Pyramid request processing cycle
    # than pyramid_tm tween egress opens a new DB session (for example a tween
    # above the pyramid_tm tween, a response callback, or a NewResponse
    # subscriber) then pyramid_tm won't close that DB session for us.
    #
    # So as a precaution add our own callback here to make sure db sessions are
    # always closed.
    @request.add_finished_callback
    def close_the_sqlalchemy_session(request):
        if len(session.transaction._connections) > 1:
            # There appear to still be open DB connections belonging to this
            # request. This shouldn't happen.
            changes = tracker.uncommitted_changes() if tracker else []
            if changes:
                msg = "closing a session with uncommitted changes %s"
                log.warning(msg,
                            changes,
                            extra={
                                "stack": True,
                                "changes": changes
                            })
            else:
                log.warning(
                    "closing an unclosed DB session (no uncommitted changes)",
                    extra={"stack": True},
                )
        # Close any unclosed DB connections.
        # This is done outside of the `if` statement above just in case: it's
        # okay to call `session.close()` even if the session does not need to
        # be closed, so just call it unconditionally so that there's no chance
        # of leaking any unclosed DB connections.
        session.close()

    return session
Beispiel #4
0
def _session(request):
    engine = request.registry["sqlalchemy.engine"]
    session = Session(bind=engine)

    # If the request has a transaction manager, associate the session with it.
    try:
        tm = request.tm
    except AttributeError:
        pass
    else:
        zope.sqlalchemy.register(session, transaction_manager=tm)

    # Track uncommitted changes so we can verify that everything was either
    # committed or rolled back when the request finishes.
    db_session_checks = request.registry.settings.get("h.db_session_checks",
                                                      True)
    if db_session_checks:
        tracker = Tracker(session)
    else:
        tracker = None

    # pyramid_tm doesn't always close the database session for us.
    #
    # For example if an exception view accesses the session and causes a new
    # transaction to be opened, pyramid_tm won't close this connection because
    # pyramid_tm's transaction has already ended before exception views are
    # executed.
    # Connections opened by NewResponse and finished callbacks aren't closed by
    # pyramid_tm either.
    #
    # So add our own callback here to make sure db sessions are always closed.
    #
    # See: https://github.com/Pylons/pyramid_tm/issues/40
    @request.add_finished_callback
    def close_the_sqlalchemy_session(request):
        changes = tracker.uncommitted_changes() if tracker else []
        if changes:
            msg = "closing a session with uncommitted changes %s"
            log.warning(msg,
                        changes,
                        extra={
                            "stack": True,
                            "changes": changes
                        })
        session.close()

    return session
Beispiel #5
0
 def tracker(self, db_session):
     return Tracker(db_session)
Beispiel #6
0
def _session(request):
    engine = request.registry['sqlalchemy.engine']
    session = Session(bind=engine)

    # If the request has a transaction manager, associate the session with it.
    try:
        tm = request.tm
    except AttributeError:
        pass
    else:
        zope.sqlalchemy.register(session, transaction_manager=tm)

    # Track uncommitted changes so we can verify that everything was either
    # committed or rolled back when the request finishes.
    db_session_checks = request.registry.settings.get('h.db_session_checks',
                                                      True)
    if db_session_checks:
        tracker = Tracker(session)
    else:
        tracker = None

    # pyramid_tm doesn't always close the database session for us.
    #
    # For example if an exception view accesses the session and causes a new
    # transaction to be opened, pyramid_tm won't close this connection because
    # pyramid_tm's transaction has already ended before exception views are
    # executed.
    # Connections opened by NewResponse and finished callbacks aren't closed by
    # pyramid_tm either.
    #
    # So add our own callback here to make sure db sessions are always closed.
    #
    # See: https://github.com/Pylons/pyramid_tm/issues/40
    @request.add_finished_callback
    def close_the_sqlalchemy_session(request):
        changes = tracker.uncommitted_changes() if tracker else []
        if changes:
            msg = 'closing a session with uncommitted changes %s'
            log.warn(msg, changes, extra={
                'stack': True,
                'changes': changes,
            })
        session.close()

        # Remove stale sqlalchemy session IDs from zope.sqlalchemy's _SESSION_STATE.
        #
        # TODO: Once https://github.com/zopefoundation/zope.sqlalchemy/pull/23
        # has been merged and we upgrade to a new version of zope.sqlalchemy
        # that includes it, we can remove this workaround.
        #
        # _SESSION_STATE is a dict whose keys are the Python object IDs of
        # sqlalchemy sessions. A session's ID is normally removed from
        # _SESSION_STATE at the end of processing that session's request. But
        # if something opens a new DB session by accessing the DB after the
        # transaction manager has committed then that session's ID is **never**
        # removed from _SESSION_STATE even after the session object has been
        # garbage collected.
        #
        # If a future request's session then happens to get the same Python
        # object ID as one of these "stale" IDs not removed from
        # _SESSION_STATE, then zope.sqlalchemy does not join that sqlalchemy
        # session to the transaction manager's transaction because it thinks it
        # has already done so. As a result, that session is never committed
        # (annotations are not saved, etc).
        #
        # To prevent that from happening we remove stale IDs from
        # _SESSION_STATE here.
        #
        dm = zope.sqlalchemy.datamanager
        if len(dm._SESSION_STATE) > 0:
            log.warn('request ended with non-empty zope.sqlalchemy state',
                     extra={
                         'data': {
                             'zope.sqlalchemy.datamanager._SESSION_STATE':
                             dm._SESSION_STATE,
                         },
                     })
            dm._SESSION_STATE = {}

    return session