Python neo4j bolt driver loses connection after context switch

259 Views Asked by At

I have a backend written in django which uses the neo4j bolt driver to communicate with the neo4j graph db.

I use a Singleton to handle the connection and the bolt driver closes the connection, whenever I access it from another location than where the connection was initially established (e.g. I open the connection in a view, access it in a signal and when I try to save in the view the connection is lost).

I’ve tried to extract the main problem I have come up with and break it down to a small piece of example code below.

I would appreciate any explanation of behavior to or even better a solution ;)

from neo4j.v1 import Driver, GraphDatabase, basic_auth, Session, Transaction


def main():
    gm = GraphMapper()
    gm.begin_atomic_transaction()

    print(f"graph connection closed before method? {gm.is_connection_closed()}") # -> false

    fill_transaction() #the context switch

    print(f"graph connection closed after method? {gm.is_connection_closed()}") # -> true

    if not gm.is_connection_closed():
        print(f"graph connection open - try to commit") # -> is never called
        gm.commit_atomic_transaction_and_close_session()


def fill_transaction():
    gm = GraphMapper()

    print(f"graph connection closed in method? {gm.is_connection_closed()}") # -> true

    gm.create_update_node("TestNode")


class GraphMapper:
    __instance = None
    __transaction = None  # type: Transaction
    __session = None  # type: Session
    __connection = None # type: Connection
    __driver = None  # type: Driver

    def __new__(cls, *args, **kwargs):
        if not isinstance(cls.__instance, cls):
            cls.__instance = object.__new__(cls, *args, **kwargs)
        return cls.__instance

    def __init__(self):
        self.__driver = GraphDatabase.driver("bolt://localhost:7687", auth=basic_auth("neo4j", "password"))

    def is_connection_closed(self):
        return self.__transaction.session._connection._closed

    def begin_atomic_transaction(self):
        self.__session = self.__driver.session()
        self.__transaction = self.__session.begin_transaction()
        self.__connection = self.__transaction.session._connection
        return self.__transaction

    def commit_atomic_transaction_and_close_session(self):
        result = self.__transaction.commit()

        self.__transaction = None
        return result

    def create_update_node(self, label):
        # Add Cypher statement to transaction

Implementation details: I have a wrapper object "GraphMapper" which encapsulates the connection, session, and transaction of the driver. and is designed as a singleton instance. A transaction is established at a point (A, e.g. a view) but I cannot complete the transaction here. I need to add additional values from location (B, e.g. a post-save signal). However, I cannot pass a reference to the "GraphMapper" A to B. Thus, I came up with the singleton implementation as explained above. I have ensured that the singleton is exact the same instance on all locations (within one request). But at the moment I exit the context (package, class or method) through a method call and retrieve the "GraphMapper" instance at the very next location, the connection is closed. I even checked the reference count to the "GraphMapper" and its connection and the garbage collector should not delete it. Seldom it says the connection is not closed. But writing to the graph results in a connection refused error.

P.S.: I know there is some useless and unnecessary code, this is for illustrative purposes only and I wanted to make sure that the garbage collector did not kill some objects.

0

There are 0 best solutions below