fix: connect_replica and read_only should be idempotent (#21674)
* fix: `connect_replica` should be idempotent Calling `connect_replica` twice ends up forgetting orginal writable replica completely. * test: replicas closes https://github.com/frappe/frappe/issues/21619
This commit is contained in:
parent
6b2bb9a2ab
commit
fefd9ac2e2
2 changed files with 43 additions and 3 deletions
|
|
@ -274,9 +274,12 @@ def connect(
|
|||
set_user("Administrator")
|
||||
|
||||
|
||||
def connect_replica():
|
||||
def connect_replica() -> bool:
|
||||
from frappe.database import get_db
|
||||
|
||||
if local and hasattr(local, "replica_db") and hasattr(local, "primary_db"):
|
||||
return False
|
||||
|
||||
user = local.conf.db_name
|
||||
password = local.conf.db_password
|
||||
port = local.conf.replica_db_port
|
||||
|
|
@ -291,6 +294,8 @@ def connect_replica():
|
|||
local.primary_db = local.db
|
||||
local.db = local.replica_db
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def get_site_config(sites_path: str | None = None, site_path: str | None = None) -> dict[str, Any]:
|
||||
"""Returns `site_config.json` combined with `sites/common_site_config.json`.
|
||||
|
|
@ -809,13 +814,17 @@ def is_whitelisted(method):
|
|||
def read_only():
|
||||
def innfn(fn):
|
||||
def wrapper_fn(*args, **kwargs):
|
||||
|
||||
# frappe.read_only could be called from nested functions, in such cases don't swap the
|
||||
# connection again.
|
||||
switched_connection = False
|
||||
if conf.read_from_replica:
|
||||
connect_replica()
|
||||
switched_connection = connect_replica()
|
||||
|
||||
try:
|
||||
retval = fn(*args, **get_newargs(fn, kwargs))
|
||||
finally:
|
||||
if local and hasattr(local, "primary_db"):
|
||||
if switched_connection and local and hasattr(local, "primary_db"):
|
||||
local.db.close()
|
||||
local.db = local.primary_db
|
||||
|
||||
|
|
|
|||
|
|
@ -930,3 +930,34 @@ class TestTransactionManagement(FrappeTestCase):
|
|||
|
||||
frappe.db.commit()
|
||||
self.assertEqual(_get_transaction_id(), _get_transaction_id())
|
||||
|
||||
|
||||
# Treat same DB as replica for tests, a separate connection will be opened
|
||||
class TestReplicaConnections(FrappeTestCase):
|
||||
def test_switching_to_replica(self):
|
||||
with patch.dict(frappe.local.conf, {"read_from_replica": 1, "replica_host": "localhost"}):
|
||||
|
||||
def db_id():
|
||||
return id(frappe.local.db)
|
||||
|
||||
write_connection = db_id()
|
||||
read_only_connection = None
|
||||
|
||||
@frappe.read_only()
|
||||
def outer():
|
||||
nonlocal read_only_connection
|
||||
read_only_connection = db_id()
|
||||
|
||||
# A new connection should be opened
|
||||
self.assertNotEqual(read_only_connection, write_connection)
|
||||
inner()
|
||||
# calling nested read only function shouldn't change connection
|
||||
self.assertEqual(read_only_connection, db_id())
|
||||
|
||||
@frappe.read_only()
|
||||
def inner():
|
||||
# calling nested read only function shouldn't change connection
|
||||
self.assertEqual(read_only_connection, db_id())
|
||||
|
||||
outer()
|
||||
self.assertEqual(write_connection, db_id())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue