Inspired_Blue
Inspired_Blue

Reputation: 2438

ZEO/ ZODB: Unable to sync transactions between client instances

Minimal Reproducible Example:

For minimal reproducible example consider this:

I first start a ZEO server with the command: runzeo -C zeo.conf. The following are the contents of my zeo.conf:

<zeo>
address 127.0.0.1:9999
</zeo>

<filestorage>
  path /path/to/testing.fs
</filestorage>

Then I create two connections to this ZEO server by running python employees.py twice on separate terminals. The contents of employees.py are:

import persistent
from ZODB import DB
from ZODB.FileStorage import FileStorage
import transaction
from ZEO.ClientStorage import ClientStorage

class Employees(persistent.Persistent):
    def __init__(self,name=None):
        self.name=name

def list_employees():
    return employees.keys()

def add_employee(name):
    if name != "" and name not in list_employees():
        employees[name] = Employees(name)
    root['employees'] = employees
    transaction.commit()

storage = ClientStorage(('localhost', 9999), server_sync = True)
db = DB(storage)
connection = db.open()
root = connection.root()

if "employees" not in root:
    root["employees"] = {}
employees=root["employees"]

while True:
    choice=input("Press:\n"
                 "'L' to list employees,\n"
                 "'A' to add an employee,\n"
                 "'Q' to quit: ")
    choice=choice.lower()

    if choice=="l":
        print(list_employees())

    elif choice=="a":
        name=input("Employee name: ")
        with transaction.manager:
            add_employee(name)

    elif choice=="q":
        break

Observed behavior:

  1. If I press L before any write transactions are made then both instances agree.
  2. In one of the instances I press A and add an employee with name (say) foo. In that instance pressing L correctly reflects the write transaction. However in the other instance pressing L shows only the old version of the database.
  3. If I quit Q the lagging instance and restart it then it shows the correct state of the database.

Expected behavior:

Both instances should be aware of transactions made by the other.

Notes:

  1. I have set server_sync = True while making storage = ClientStorage(('localhost', 9999), server_sync = True). But this doesn't fix.
  2. I also tried to set cache_size = 0 but that didn't fix it either.
  3. On a Ubuntu 22.04.1 LTS machine with the package versions: ZEO==5.4.0, ZODB==5.8.0 and Python==3.10.6.

Please help. The documentation of ZEO server is terse and somewhat difficult to follow.

Upvotes: 0

Views: 42

Answers (1)

Lee
Lee

Reputation: 1427

Instead of using normal dict you should use PerstitentMapping (or BTree) to store your employees else the object you edit would be local a local copy that you manipulate in both clients. In your code that copy would only be refreshed if you restart a client. The problem get even worse with your code the last client that writes overwrites all other clients states with it own local copy. See the comments for the changes needed to fix the problem.

import persistent
from ZODB import DB
from ZODB.FileStorage import FileStorage
import transaction
from ZEO.ClientStorage import ClientStorage

class Employees(persistent.Persistent):
    def __init__(self,name=None):
        self.name=name

def list_employees():
    #convert KeyView to list
    return list(employees.keys())

def add_employee(name):
    if name != "" and name not in list_employees():
        employees[name] = Employees(name)

storage = ClientStorage(('localhost', 9999), server_sync = True)
db = DB(storage)
connection = db.open()
root = connection.root()

if "employees" not in root:
    with transaction.manager:
        #use persitent mapping instead of normal dict
        root["employees"] = persistent.mapping.PersistentMapping()
employees=root["employees"]    

while True:
    choice=input("Press:\n"
                 "'L' to list employees,\n"
                 "'A' to add an employee,\n"
                 "'Q' to quit: ")
    choice=choice.lower()

    if choice=="l": 
        #always use transaction
        with transaction.manager:
            print(list_employees())

    elif choice=="a":
        name=input("Employee name: ")
        with transaction.manager:
            add_employee(name)

    elif choice=="q":
        break

Upvotes: 0

Related Questions