-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AsyncConnectionPool in a spawned process gets stuck when acquiring a connection from the pool two or more times #457
Comments
Can you share code that demonstrates the problem? Creation of a pool and use of the connections in a pool should only occur in the same process. No mixing and matching allowed! |
yes, each process creates own pool into init function. Here is a very simplified example to describe the concept of how it is initialized and used in the child process:
This logic works on cx_Oracle + cx_Oracle_async. If you need the real working example, I'll create it, the real logic is very huge to try to copy it as an example :) |
I had to tweak this a fair bit to make it runnable. Does this hang for you? It works for me. import asyncio
from concurrent.futures import ProcessPoolExecutor
import oracledb
class Worker:
def __init__(self, ctx, config):
self.loop = asyncio.new_event_loop()
self.ctx = ctx
self.config = config
asyncio.set_event_loop(self.loop)
self.loop.run_until_complete(self.asyncInit())
async def asyncInit(self):
self.pool = oracledb.create_pool_async(
dsn="host/service_name",
user="user",
password="password",
min=2,
max=10,
increment=1,
getmode=oracledb.SPOOL_ATTRVAL_NOWAIT,
)
def run(self, coro):
self.loop.run_until_complete(coro)
async def runTask(self, func, *args):
await func(*args)
async def acquire(self):
try:
return await self.pool.acquire()
except oracledb.DatabaseError as ex:
# the logic when we have no ready connection
pass
async def release(self, con):
await self.pool.release(con)
def initSubprocess(ctx, config):
global w
w = Worker(ctx, config)
def runTask(func, args):
w.run(w.runTask(func, args))
async def taskWorker(params):
con = await w.acquire()
async with con:
print("version:", con.version)
# real payload
ctx = {}
config = {}
params = {}
p = ProcessPoolExecutor(
max_workers=4,
initializer=initSubprocess,
initargs=(ctx, config),
)
result = p.submit(runTask, taskWorker, params)
print("result 1:", result.result())
result = p.submit(runTask, taskWorker, params)
print("result 2:", result.result())
result = p.submit(runTask, taskWorker, params)
print("result 3:", result.result())
print("Done!") Note that I simplified the taskWorker() by using an asynchronous context manager -- that ensures that the connection is returned to the pool, regardless of success or failure. |
I expanded the test script but couldn't reproduce the real issue. Also, I added the logging before and after the connection acquiring call and I observed 3 times the same behavior after restart: my current version of the script that unfortunately does not reproduce issue:
|
I have tested my web application with oracledb-3.0.0b1. The application uses ProcessPoolExecutor with mp_context=multiprocessing.get_context('spawn') to start background tasks. Each child process creates its own AsyncConnectionPool, which is used to process submitted tasks.
I observed that the child process freezes when acquiring a connection from the pool, usually on the second received task.
Additionally, when I restarted the application, I encountered a CancelledError in the child process:
It's a very strange place for CancelledError because I create a pool as
oracledb.create_pool_async(getmode=oracledb.SPOOL_ATTRVAL_NOWAIT)
For release connection, I use the bellow way:
await pool.release(con)
But this explains why the request processing in child processes gets stuck every time.
I suspect that something goes wrong with oracledb initialization when a child process is spawned.
Could it be that additional initialization is required in the child process for oracledb (thin mode) before creating the AsyncConnectionPool?
I have observed this behavior only on Linux. On Windows, ProcessPoolExecutor spawns a new child process for each task and terminates it immediately after the task is completed.
The text was updated successfully, but these errors were encountered: