Concurrency

Let us see how sheraf cowboys behave in parallelize contexts.

>>> class Cowboy(sheraf.Model):
...     table = "cowboys"
...     gunskill = sheraf.IntegerAttribute()
...
>>> db = sheraf.Database("zeo://localhost:{}".format(zeo_port))
...
>>> with sheraf.connection(commit=True):
...     george = Cowboy.create()

Threading

The ZODB documentation about concurrency states that database Connection, ITransactionManager and ITransaction are not thread-safe. However ZODB DB objects can be shared between threads.

This means that it is possible to create a Database object once, and then share it on several threads. However each thread should use its own connection context:

>>> import threading
>>> def practice_gun(cowboy_id):
...     # The database is available in children thread, but they
...     # need to open their own connection contexts.
...     with sheraf.connection(commit=True):
...         cowboy = Cowboy.read(cowboy_id)
...         cowboy.gunskill = cowboy.gunskill + 1000
...
>>> practice_session = threading.Thread(target=practice_gun, args=(george.id,))
>>> practice_session.start()
>>> practice_session.join()
...
>>> with sheraf.connection():
...     Cowboy.read(george.id).gunskill
1000

Opening a thread within a connection context will produce various unexpected behaviors.

Multiprocessing

When using multiprocessing, the behavior is a bit different. The Database are not shared between processes.

>>> import multiprocessing
>>> practice_session = multiprocessing.Process(target=practice_gun, args=(george.id,))
>>> practice_session.start()
>>> practice_session.join()
>>> practice_session.exitcode
1

The connection context in the practice_gun function has raised a KeyError exception because in this new process, no database has been defined. Fortunately there is a simple solution to this. The database needs to be redefined in the new process:

>>> def recreate_db_and_practice_gun(cowboy_id):
...     # The database is re-created in the child process
...     db = sheraf.Database("zeo://localhost:{}".format(zeo_port))
...
...     with sheraf.connection(commit=True):
...         cowboy = Cowboy.read(cowboy_id)
...         cowboy.gunskill = cowboy.gunskill + 1000
...     db.close()
...
>>> practice_session = multiprocessing.Process(target=recreate_db_and_practice_gun, args=(george.id,))
>>> practice_session.start()
>>> practice_session.join()
...
>>> with sheraf.connection():
...     Cowboy.read(george.id).gunskill
2000

Note

Remember that FileStorage, MappingStorage and DemoStorage cannot be used by several processes.