I have the following code, all pretty simple:
from multiprocessing import Process, Lock
from multiprocessing.managers import BaseManager
class DatabaseConnection:
def __init__(self, conn_id):
self.id = conn_id
def __repr__(self):
return f'connection(id={self.id})'
class DatabaseConnectionPool:
def __init__(self):
self.mutex = Lock()
self.max_connections = 10
self.ready_pool = [DatabaseConnection(i) for i in range(self.max_connections)]
self.leased_pool = []
def get(self):
if not self.ready_pool:
raise Exception('no connections available')
conn = self.ready_pool[-1]
self.ready_pool.pop()
self.leased_pool.append(conn)
return conn
class Manager(BaseManager): pass
Manager.register('DatabaseConnectionPool', DatabaseConnectionPool)
def proc(pool):
try:
conn = pool.get()
print(f'leased {conn}')
except:
print(f'failed to lease connection')
if __name__ == '__main__':
manager = Manager()
manager.start()
pool = manager.DatabaseConnectionPool()
procs = []
for _ in range(11):
p = Process(target=proc, args=(pool,))
procs.append(p)
p.start()
for p in procs:
p.join()
when I run this, I get:
leased connection(id=9)
leased connection(id=8)
leased connection(id=7)
leased connection(id=6)
leased connection(id=5)
leased connection(id=4)
leased connection(id=3)
leased connection(id=2)
leased connection(id=1)
leased connection(id=0)
failed to lease connection
I am essentially creating a DatabaseConnectionPool
class shared across a bunch of processes, then having each process retrieve a DatabaseConnection
object from the pool, expecting this to cause a race condition but I can't seem to provoke one here.
Specifically, I would expect the line conn = self.ready_pool[-1]
to result in a race condition such that some processes return connections with duplicated ids since I don't have a mutex around the DatabaseConnectionPool.Get()
method.
Am I understanding something wrong here? Since the DatabaseConnectionPool
is shared, each process is pulling from the same list and the following two lines are not atomic (purposefully) as far as I know.
conn = self.ready_pool[-1]
self.ready_pool.pop()