Fix for AsyncDatabase and parallel transactions #888 ?#889
Conversation
d2f39ab to
f83d85b
Compare
Following the pattern of neomodel.sync_.core.Database where all instance properties are threading-local (except global mappings _NODE_CLASS_REGISTRY and _DB_SPECIFIC_CLASS_REGISTRY), I've converted the same properties of AsyncDatabase to ContextVars (to be context-local instance properties).
f83d85b to
11ab178
Compare
|
I'm wondering if all properties of AsyncDatabase should be context-aware? |
|
Disclaimer: I'm not a maintainer of neomodel, but I've kindly been asked for input. So here goes:
From a general maintenance perspective I suggest to keep the async and sync implementations as close as possible from a mental perspective. So at least for this PR, I suggest to turn everything that's thread local in sync into context local in async. The PR could (optional) even go a step further and also use Another thing to keep in mind is that async Python has some gotchas/rough edges. What do you think this snippet does? import asyncio
from contextvars import ContextVar
var = ContextVar('var', default='default')
def print_var():
print(f'var: {var.get()}')
async def task():
print("task:")
var.set('task')
print_var()
async def main():
print("main:")
print_var()
await asyncio.wait_for(task(), timeout=1)
print("main:")
print_var()
if __name__ == '__main__':
asyncio.run(main())Does This is a trick question... Until Python 3.11 it doesn't, because async def save_node():
node = TestNode(name="Cool node")
await node.save()
async def main():
async with adb.transaction:
# don't want to wait for ever to create the node
asyncio.wait_for(save_node, timeout=1)That reads alright and does what it looks like on the tin for newer Python version, but on older Python verions I don't think this is a blocker for this PR, but it needs consideration and probably some warning in the docs. |
Indeed it is auto-transpiled, with ways to have custom code for either async or sync ; both of which mechanisms I took from the Neo4j python driver you know so well ! Based on your comments (thanks a ton), then it sounds like moving everything in AsyncDatabase to ContextVar and letting it get transpiled works. I will update the PR with the transpiling, then test it, and set it for merge on the next "major" release - in case there are breaking changes we did not catch. Is that OK @DenesPal ?
Well, what can I say ? Checking that the transpile happened is a good idea ! I have been relying too much on my pre-commit actions here. |
|
So... Looking good for me now ; the new tests pass ; no existing tests were broken, except those 3 that look totally unrelated, and work perfectly fine in my local environment ! Gah ! (Note I finally managed to fix the Aura tests though) |
…abase-transactions
…abase-transactions
|
OK tests are fixed, it was an issue with a previous PR that had duplicated code which I had missed |
…abase-transactions
|



async_.core.AsyncDatabase was incorrectly subclassed from threading.local, that did not make it's properties context-local, leaking state (like session and transaction states) across async contexts.
I am assuming that sync_.core.Database implementation is correct.
Apart from the two class-level global mappings (_NODE_CLASS_REGISTRY and _DB_SPECIFIC_CLASS_REGISTRY), all instance properties of Database are threading-local.
So I've converted the same instance properties of AsyncDatabase to ContextVar @property-s (because ContextVars work with .get & .set).