Skip to content
Permalink
Browse files

Make nested transaction thread aware

Previously, if create_transaction() was called from different threads or
greenthreads, always a single transaction was returned even when the
thread was different.

This patch makes a map between thread ID and a transaction therefore
nested transaction is returned only for the same threads.

Change-Id: I884e7973d05434d798e0aeaa0f1f826905a8113e
Closes-bug: 1793499
Closes-bug: 1735154
  • Loading branch information
cubeek authored and danalsan committed Sep 20, 2018
1 parent 1c58eac commit 9c07fe8310bec8939cde99018d10482a7c820b61
Showing with 55 additions and 6 deletions.
  1. +14 −6 ovsdbapp/api.py
  2. +41 −0 ovsdbapp/tests/unit/test_api.py
@@ -16,6 +16,11 @@
import contextlib

import six
try:
# Python 3 no longer has thread module
import thread # noqa
except ImportError:
import threading as thread


@six.add_metaclass(abc.ABCMeta)
@@ -67,7 +72,8 @@ def __exit__(self, exc_type, exc_val, tb):
@six.add_metaclass(abc.ABCMeta)
class API(object):
def __init__(self):
self._nested_txn = None
# Mapping between a (green)thread and its transaction.
self._nested_txns_map = {}

@abc.abstractmethod
def create_transaction(self, check_error=False, log_errors=True, **kwargs):
@@ -92,16 +98,18 @@ def transaction(self, check_error=False, log_errors=True, **kwargs):
:returns: Either a new transaction or an existing one.
:rtype: :class:`Transaction`
"""
if self._nested_txn:
yield self._nested_txn
else:
cur_thread_id = thread.get_ident()

try:
yield self._nested_txns_map[cur_thread_id]
except KeyError:
with self.create_transaction(
check_error, log_errors, **kwargs) as txn:
self._nested_txn = txn
self._nested_txns_map[cur_thread_id] = txn
try:
yield txn
finally:
self._nested_txn = None
del self._nested_txns_map[cur_thread_id]

@abc.abstractmethod
def db_create(self, table, **col_values):
@@ -14,10 +14,24 @@

import mock
import testtools
import time

from ovsdbapp import api
from ovsdbapp.tests import base

try:
import eventlet

def create_thread(executable):
eventlet.spawn_n(executable)

except ImportError:
import threading

def create_thread(executable):
thread = threading.Thread(target=executable)
thread.start()


class FakeTransaction(object):
def __enter__(self):
@@ -60,3 +74,30 @@ class TestException(Exception):

with self.api.transaction() as txn2:
self.assertIsNot(txn1, txn2)

def test_transaction_nested_multiple_threads(self):
shared_resource = []

def thread1():
with self.api.transaction() as txn:
shared_resource.append(txn)
while len(shared_resource) == 1:
time.sleep(0.1)
shared_resource.append(0)

def thread2():
while len(shared_resource) != 1:
time.sleep(0.1)
with self.api.transaction() as txn:
shared_resource.append(txn)
shared_resource.append(0)

create_thread(thread1)
create_thread(thread2)

while len(shared_resource) != 4:
time.sleep(0.1)

txn1, txn2 = shared_resource[:2]

self.assertNotEqual(txn1, txn2)

0 comments on commit 9c07fe8

Please sign in to comment.