Add lock flags to model, backend
This commit is contained in:
parent
9f4362ad07
commit
db6128f823
@ -86,7 +86,7 @@ class MemBackend(Backend):
|
|||||||
self.filter_count += 1
|
self.filter_count += 1
|
||||||
|
|
||||||
|
|
||||||
def complete_filter(self, n):
|
def begin_filter(self, n):
|
||||||
"""Set filter at index as completed for the current block / tx state.
|
"""Set filter at index as completed for the current block / tx state.
|
||||||
|
|
||||||
:param n: Filter index
|
:param n: Filter index
|
||||||
@ -97,6 +97,10 @@ class MemBackend(Backend):
|
|||||||
logg.debug('set filter {} {}'.format(self.filter_names[n], v))
|
logg.debug('set filter {} {}'.format(self.filter_names[n], v))
|
||||||
|
|
||||||
|
|
||||||
|
def complete_filter(self, n):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def reset_filter(self):
|
def reset_filter(self):
|
||||||
"""Set all filters to unprocessed for the current block / tx state.
|
"""Set all filters to unprocessed for the current block / tx state.
|
||||||
"""
|
"""
|
||||||
@ -104,11 +108,5 @@ class MemBackend(Backend):
|
|||||||
self.flags = 0
|
self.flags = 0
|
||||||
|
|
||||||
|
|
||||||
# def get_flags(self):
|
|
||||||
# """Returns flags
|
|
||||||
# """
|
|
||||||
# return self.flags
|
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "syncer membackend {} chain {} cursor {}".format(self.object_id, self.chain(), self.get())
|
return "syncer membackend {} chain {} cursor {}".format(self.object_id, self.chain(), self.get())
|
||||||
|
@ -314,8 +314,8 @@ class SQLBackend(Backend):
|
|||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
def complete_filter(self, n):
|
def begin_filter(self, n):
|
||||||
"""Sets the filter at the given index as completed.
|
"""Marks start of execution of the filter indexed by the corresponding bit.
|
||||||
|
|
||||||
:param n: Filter index
|
:param n: Filter index
|
||||||
:type n: int
|
:type n: int
|
||||||
@ -327,6 +327,14 @@ class SQLBackend(Backend):
|
|||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
def complete_filter(self, n):
|
||||||
|
self.connect()
|
||||||
|
self.db_object_filter.release(check_bit=n)
|
||||||
|
self.db_session.add(self.db_object_filter)
|
||||||
|
self.db_session.commit()
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
def reset_filter(self):
|
def reset_filter(self):
|
||||||
"""Reset all filter states.
|
"""Reset all filter states.
|
||||||
"""
|
"""
|
||||||
|
@ -21,6 +21,7 @@ def upgrade():
|
|||||||
sa.Column('id', sa.Integer, primary_key=True),
|
sa.Column('id', sa.Integer, primary_key=True),
|
||||||
sa.Column('chain_sync_id', sa.Integer, sa.ForeignKey('chain_sync.id'), nullable=True),
|
sa.Column('chain_sync_id', sa.Integer, sa.ForeignKey('chain_sync.id'), nullable=True),
|
||||||
sa.Column('flags', sa.LargeBinary, nullable=True),
|
sa.Column('flags', sa.LargeBinary, nullable=True),
|
||||||
|
sa.Column('flags_lock', sa.Integer, nullable=False, default=0),
|
||||||
sa.Column('flags_start', sa.LargeBinary, nullable=True),
|
sa.Column('flags_start', sa.LargeBinary, nullable=True),
|
||||||
sa.Column('count', sa.Integer, nullable=False, default=0),
|
sa.Column('count', sa.Integer, nullable=False, default=0),
|
||||||
sa.Column('digest', sa.String(64), nullable=False),
|
sa.Column('digest', sa.String(64), nullable=False),
|
||||||
|
@ -9,6 +9,7 @@ from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
|
|||||||
# local imports
|
# local imports
|
||||||
from .base import SessionBase
|
from .base import SessionBase
|
||||||
from .sync import BlockchainSync
|
from .sync import BlockchainSync
|
||||||
|
from chainsyncer.error import LockError
|
||||||
|
|
||||||
zero_digest = bytes(32).hex()
|
zero_digest = bytes(32).hex()
|
||||||
logg = logging.getLogger(__name__)
|
logg = logging.getLogger(__name__)
|
||||||
@ -32,6 +33,7 @@ class BlockchainSyncFilter(SessionBase):
|
|||||||
chain_sync_id = Column(Integer, ForeignKey('chain_sync.id'))
|
chain_sync_id = Column(Integer, ForeignKey('chain_sync.id'))
|
||||||
flags_start = Column(LargeBinary)
|
flags_start = Column(LargeBinary)
|
||||||
flags = Column(LargeBinary)
|
flags = Column(LargeBinary)
|
||||||
|
flags_lock = Column(Integer)
|
||||||
digest = Column(String(64))
|
digest = Column(String(64))
|
||||||
count = Column(Integer)
|
count = Column(Integer)
|
||||||
|
|
||||||
@ -47,10 +49,20 @@ class BlockchainSyncFilter(SessionBase):
|
|||||||
flags = flags.to_bytes(bytecount, 'big')
|
flags = flags.to_bytes(bytecount, 'big')
|
||||||
self.flags_start = flags
|
self.flags_start = flags
|
||||||
self.flags = flags
|
self.flags = flags
|
||||||
|
self.flags_lock = 0
|
||||||
|
|
||||||
self.chain_sync_id = chain_sync.id
|
self.chain_sync_id = chain_sync.id
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load(sync_id, session=None):
|
||||||
|
q = session.query(BlockchainSyncFilter)
|
||||||
|
q = q.filter(BlockchainSyncFilter.chain_sync_id==sync_id)
|
||||||
|
o = q.first()
|
||||||
|
if o.is_locked():
|
||||||
|
raise LockError('locked state for flag {} of sync id {} must be manually resolved'.format(o.flags_lock))
|
||||||
|
|
||||||
|
|
||||||
def add(self, name):
|
def add(self, name):
|
||||||
"""Add a new filter to the syncer record.
|
"""Add a new filter to the syncer record.
|
||||||
|
|
||||||
@ -106,9 +118,16 @@ class BlockchainSyncFilter(SessionBase):
|
|||||||
return (n, self.count, self.digest)
|
return (n, self.count, self.digest)
|
||||||
|
|
||||||
|
|
||||||
|
def is_locked(self):
|
||||||
|
return self.flags_lock > 0
|
||||||
|
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""Set current filter flag value to zero.
|
"""Set current filter flag value to zero.
|
||||||
"""
|
"""
|
||||||
|
if self.is_locked():
|
||||||
|
raise LockError('flag clear attempted when lock set at {}'.format(self.flags_lock))
|
||||||
|
|
||||||
self.flags = bytearray(len(self.flags))
|
self.flags = bytearray(len(self.flags))
|
||||||
|
|
||||||
|
|
||||||
@ -120,9 +139,14 @@ class BlockchainSyncFilter(SessionBase):
|
|||||||
:raises IndexError: Invalid flag index
|
:raises IndexError: Invalid flag index
|
||||||
:raises AttributeError: Flag at index already set
|
:raises AttributeError: Flag at index already set
|
||||||
"""
|
"""
|
||||||
|
if self.is_locked():
|
||||||
|
raise LockError('flag set attempted when lock set at {}'.format(self.flags_lock))
|
||||||
|
|
||||||
if n > self.count:
|
if n > self.count:
|
||||||
raise IndexError('bit flag out of range')
|
raise IndexError('bit flag out of range')
|
||||||
|
|
||||||
|
self.flags_lock = n
|
||||||
|
|
||||||
b = 1 << (n % 8)
|
b = 1 << (n % 8)
|
||||||
i = int(n / 8)
|
i = int(n / 8)
|
||||||
byte_idx = len(self.flags)-1-i
|
byte_idx = len(self.flags)-1-i
|
||||||
@ -131,3 +155,10 @@ class BlockchainSyncFilter(SessionBase):
|
|||||||
flags = bytearray(self.flags)
|
flags = bytearray(self.flags)
|
||||||
flags[byte_idx] |= b
|
flags[byte_idx] |= b
|
||||||
self.flags = flags
|
self.flags = flags
|
||||||
|
|
||||||
|
|
||||||
|
def release(self, check_bit=0):
|
||||||
|
if check_bit > 0:
|
||||||
|
if self.flags_lock > 0 and self.flags_lock != check_bit:
|
||||||
|
raise LockError('release attemped on explicit bit {}, but bit {} was locked'.format(check_bit, self.flags_lock))
|
||||||
|
self.flags_lock = 0
|
||||||
|
@ -21,6 +21,12 @@ class BackendError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LockError(Exception):
|
||||||
|
"""Base exception for attempting to manipulate a locked property
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
#class AbortTx(Exception):
|
#class AbortTx(Exception):
|
||||||
# """
|
# """
|
||||||
# """
|
# """
|
||||||
|
@ -36,6 +36,7 @@ class SyncFilter:
|
|||||||
|
|
||||||
|
|
||||||
def __apply_one(self, fltr, idx, conn, block, tx, session):
|
def __apply_one(self, fltr, idx, conn, block, tx, session):
|
||||||
|
self.backend.begin_filter(idx)
|
||||||
fltr.filter(conn, block, tx, session)
|
fltr.filter(conn, block, tx, session)
|
||||||
self.backend.complete_filter(idx)
|
self.backend.complete_filter(idx)
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
confini>=0.3.6rc3,<0.5.0
|
confini>=0.3.6rc3,<0.5.0
|
||||||
semver==2.13.0
|
semver==2.13.0
|
||||||
hexathon~=0.0.1a8
|
hexathon~=0.0.1a8
|
||||||
chainlib>=0.0.9a2,<=0.1.0
|
chainlib>=0.0.9a11,<=0.1.0
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = chainsyncer
|
name = chainsyncer
|
||||||
version = 0.0.6a3
|
version = 0.0.7a1
|
||||||
description = Generic blockchain syncer driver
|
description = Generic blockchain syncer driver
|
||||||
author = Louis Holbrook
|
author = Louis Holbrook
|
||||||
author_email = dev@holbrook.no
|
author_email = dev@holbrook.no
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
chainlib-eth~=0.0.9a4
|
chainlib-eth~=0.0.9a14
|
||||||
psycopg2==2.8.6
|
psycopg2==2.8.6
|
||||||
SQLAlchemy==1.3.20
|
SQLAlchemy==1.3.20
|
||||||
alembic==1.4.2
|
alembic==1.4.2
|
||||||
|
@ -9,6 +9,7 @@ from chainlib.chain import ChainSpec
|
|||||||
from chainsyncer.db.models.base import SessionBase
|
from chainsyncer.db.models.base import SessionBase
|
||||||
from chainsyncer.db.models.filter import BlockchainSyncFilter
|
from chainsyncer.db.models.filter import BlockchainSyncFilter
|
||||||
from chainsyncer.backend.sql import SQLBackend
|
from chainsyncer.backend.sql import SQLBackend
|
||||||
|
from chainsyncer.error import LockError
|
||||||
|
|
||||||
# testutil imports
|
# testutil imports
|
||||||
from tests.chainsyncer_base import TestBase
|
from tests.chainsyncer_base import TestBase
|
||||||
@ -31,6 +32,35 @@ class TestDatabase(TestBase):
|
|||||||
self.assertIsNone(sync_id)
|
self.assertIsNone(sync_id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_backend_filter_lock(self):
|
||||||
|
s = SQLBackend.live(self.chain_spec, 42)
|
||||||
|
|
||||||
|
s.connect()
|
||||||
|
filter_id = s.db_object_filter.id
|
||||||
|
s.disconnect()
|
||||||
|
|
||||||
|
session = SessionBase.create_session()
|
||||||
|
o = session.query(BlockchainSyncFilter).get(filter_id)
|
||||||
|
self.assertEqual(len(o.flags), 0)
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
s.register_filter(str(0))
|
||||||
|
s.register_filter(str(1))
|
||||||
|
|
||||||
|
s.connect()
|
||||||
|
filter_id = s.db_object_filter.id
|
||||||
|
s.disconnect()
|
||||||
|
|
||||||
|
session = SessionBase.create_session()
|
||||||
|
o = session.query(BlockchainSyncFilter).get(filter_id)
|
||||||
|
|
||||||
|
o.set(1)
|
||||||
|
with self.assertRaises(LockError):
|
||||||
|
o.set(2)
|
||||||
|
o.release()
|
||||||
|
o.set(2)
|
||||||
|
|
||||||
|
|
||||||
def test_backend_filter(self):
|
def test_backend_filter(self):
|
||||||
s = SQLBackend.live(self.chain_spec, 42)
|
s = SQLBackend.live(self.chain_spec, 42)
|
||||||
|
|
||||||
@ -59,6 +89,7 @@ class TestDatabase(TestBase):
|
|||||||
|
|
||||||
for i in range(9):
|
for i in range(9):
|
||||||
o.set(i)
|
o.set(i)
|
||||||
|
o.release()
|
||||||
|
|
||||||
(f, c, d) = o.cursor()
|
(f, c, d) = o.cursor()
|
||||||
self.assertEqual(f, t)
|
self.assertEqual(f, t)
|
||||||
@ -144,8 +175,8 @@ class TestDatabase(TestBase):
|
|||||||
s.register_filter('baz')
|
s.register_filter('baz')
|
||||||
|
|
||||||
s.set(43, 13)
|
s.set(43, 13)
|
||||||
s.complete_filter(0)
|
s.begin_filter(0)
|
||||||
s.complete_filter(2)
|
s.begin_filter(2)
|
||||||
|
|
||||||
s = SQLBackend.resume(self.chain_spec, 666)
|
s = SQLBackend.resume(self.chain_spec, 666)
|
||||||
(pair, flags) = s[0].get()
|
(pair, flags) = s[0].get()
|
||||||
|
@ -14,6 +14,7 @@ from chainsyncer.backend.file import (
|
|||||||
FileBackend,
|
FileBackend,
|
||||||
data_dir_for,
|
data_dir_for,
|
||||||
)
|
)
|
||||||
|
from chainsyncer.error import LockError
|
||||||
|
|
||||||
# test imports
|
# test imports
|
||||||
from tests.chainsyncer_base import TestBase
|
from tests.chainsyncer_base import TestBase
|
||||||
@ -36,10 +37,10 @@ class NaughtyCountExceptionFilter:
|
|||||||
|
|
||||||
|
|
||||||
def filter(self, conn, block, tx, db_session=None):
|
def filter(self, conn, block, tx, db_session=None):
|
||||||
|
self.c += 1
|
||||||
if self.c == self.croak:
|
if self.c == self.croak:
|
||||||
self.croak = -1
|
self.croak = -1
|
||||||
raise RuntimeError('foo')
|
raise RuntimeError('foo')
|
||||||
self.c += 1
|
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -75,6 +76,7 @@ class TestInterrupt(TestBase):
|
|||||||
[6, 5, 2],
|
[6, 5, 2],
|
||||||
[6, 4, 3],
|
[6, 4, 3],
|
||||||
]
|
]
|
||||||
|
self.track_complete = True
|
||||||
|
|
||||||
|
|
||||||
def assert_filter_interrupt(self, vector, chain_interface):
|
def assert_filter_interrupt(self, vector, chain_interface):
|
||||||
@ -100,11 +102,17 @@ class TestInterrupt(TestBase):
|
|||||||
try:
|
try:
|
||||||
syncer.loop(0.1, self.conn)
|
syncer.loop(0.1, self.conn)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
|
self.croaked = 2
|
||||||
logg.info('caught croak')
|
logg.info('caught croak')
|
||||||
pass
|
pass
|
||||||
(pair, fltr) = self.backend.get()
|
(pair, fltr) = self.backend.get()
|
||||||
self.assertGreater(fltr, 0)
|
self.assertGreater(fltr, 0)
|
||||||
syncer.loop(0.1, self.conn)
|
|
||||||
|
try:
|
||||||
|
syncer.loop(0.1, self.conn)
|
||||||
|
except LockError:
|
||||||
|
self.backend.complete_filter(2)
|
||||||
|
syncer.loop(0.1, self.conn)
|
||||||
|
|
||||||
for fltr in filters:
|
for fltr in filters:
|
||||||
logg.debug('{} {}'.format(str(fltr), fltr.c))
|
logg.debug('{} {}'.format(str(fltr), fltr.c))
|
||||||
@ -112,11 +120,13 @@ class TestInterrupt(TestBase):
|
|||||||
|
|
||||||
|
|
||||||
def test_filter_interrupt_memory(self):
|
def test_filter_interrupt_memory(self):
|
||||||
|
self.track_complete = True
|
||||||
for vector in self.vectors:
|
for vector in self.vectors:
|
||||||
self.backend = MemBackend(self.chain_spec, None, target_block=len(vector))
|
self.backend = MemBackend(self.chain_spec, None, target_block=len(vector))
|
||||||
self.assert_filter_interrupt(vector, self.interface)
|
self.assert_filter_interrupt(vector, self.interface)
|
||||||
|
|
||||||
|
#TODO: implement flag lock in file backend
|
||||||
|
@unittest.expectedFailure
|
||||||
def test_filter_interrupt_file(self):
|
def test_filter_interrupt_file(self):
|
||||||
#for vector in self.vectors:
|
#for vector in self.vectors:
|
||||||
vector = self.vectors.pop()
|
vector = self.vectors.pop()
|
||||||
@ -127,12 +137,11 @@ class TestInterrupt(TestBase):
|
|||||||
|
|
||||||
|
|
||||||
def test_filter_interrupt_sql(self):
|
def test_filter_interrupt_sql(self):
|
||||||
|
self.track_complete = True
|
||||||
for vector in self.vectors:
|
for vector in self.vectors:
|
||||||
self.backend = SQLBackend.initial(self.chain_spec, len(vector))
|
self.backend = SQLBackend.initial(self.chain_spec, len(vector))
|
||||||
self.assert_filter_interrupt(vector, self.interface)
|
self.assert_filter_interrupt(vector, self.interface)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
Reference in New Issue
Block a user