44from uuid import uuid4
55
66from tinydb import Query , TinyDB
7+ from tinyrecord .transaction import transaction
78
89from eval_protocol .event_bus .event_bus_database import EventBusDatabase
910from eval_protocol .event_bus .logger import logger
@@ -15,6 +16,9 @@ class TinyDBEventBusDatabase(EventBusDatabase):
1516
1617 Stores data as plain JSON files, which are human-readable and
1718 don't suffer from SQLite's binary format corruption issues.
19+
20+ Uses tinyrecord for atomic transactions to handle concurrent access
21+ from multiple processes safely.
1822 """
1923
2024 def __init__ (self , db_path : str ):
@@ -27,24 +31,26 @@ def __init__(self, db_path: str):
2731 self ._table = self ._db .table ("events" )
2832
2933 def publish_event (self , event_type : str , data : Any , process_id : str ) -> None :
30- """Publish an event to the database."""
34+ """Publish an event to the database using atomic transaction ."""
3135 try :
3236 # Serialize data, handling pydantic models
3337 if hasattr (data , "model_dump" ):
3438 serialized_data = data .model_dump (mode = "json" , exclude_none = True )
3539 else :
3640 serialized_data = data
3741
38- self ._table .insert (
39- {
40- "event_id" : str (uuid4 ()),
41- "event_type" : event_type ,
42- "data" : serialized_data ,
43- "timestamp" : time .time (),
44- "process_id" : process_id ,
45- "processed" : False ,
46- }
47- )
42+ document = {
43+ "event_id" : str (uuid4 ()),
44+ "event_type" : event_type ,
45+ "data" : serialized_data ,
46+ "timestamp" : time .time (),
47+ "process_id" : process_id ,
48+ "processed" : False ,
49+ }
50+
51+ # Use tinyrecord transaction for atomic, concurrent-safe insert
52+ with transaction (self ._table ) as tr :
53+ tr .insert (document )
4854 except Exception as e :
4955 logger .warning (f"Failed to publish event to database: { e } " )
5056
@@ -83,21 +89,23 @@ def get_unprocessed_events(self, process_id: str) -> List[dict]:
8389 return []
8490
8591 def mark_event_processed (self , event_id : str ) -> None :
86- """Mark an event as processed."""
92+ """Mark an event as processed using atomic transaction ."""
8793 try :
8894 Event = Query ()
89- self ._table .update ({"processed" : True }, Event .event_id == event_id )
95+ with transaction (self ._table ) as tr :
96+ tr .update ({"processed" : True }, Event .event_id == event_id )
9097 except Exception as e :
9198 logger .debug (f"Failed to mark event as processed: { e } " )
9299
93100 def cleanup_old_events (self , max_age_hours : int = 24 ) -> None :
94- """Clean up old processed events."""
101+ """Clean up old processed events using atomic transaction ."""
95102 try :
96- # Reload table from disk to see latest data before cleanup
97- self ._table ._read_table ()
103+ # Clear cache to see latest data before cleanup
104+ self ._table .clear_cache ()
98105
99106 cutoff_time = time .time () - (max_age_hours * 3600 )
100107 Event = Query ()
101- self ._table .remove ((Event .processed == True ) & (Event .timestamp < cutoff_time )) # noqa: E712
108+ with transaction (self ._table ) as tr :
109+ tr .remove ((Event .processed == True ) & (Event .timestamp < cutoff_time )) # noqa: E712
102110 except Exception as e :
103111 logger .debug (f"Failed to cleanup old events: { e } " )
0 commit comments