@@ -160,11 +160,8 @@ class _StoreBase:
160160 synchronizer), reads serve from memory and the persistent store is no longer
161161 read from.
162162
163- This base holds the in-memory store, dependency tracking, listeners, the
164- active-store swap, and the changeset-to-memory apply. It never references a
165- persistent store: subclasses own their concretely-typed store and supply the
166- persist step through the ``_stage_persist_full``/``_stage_persist_delta``
167- hooks and the ``_on_memory_store_active`` hook.
163+ This base owns the in-memory store, dependency tracking, listeners, and the
164+ active-store swap. It holds no persistent store of its own.
168165 """
169166
170167 def __init__ (
@@ -198,6 +195,10 @@ def __init__(
198195 # Thread synchronization
199196 self ._lock = threading .RLock ()
200197
198+ # True if the data in the memory store may be written to the persistent
199+ # store. Set on each apply from its persist flag.
200+ self ._persist = False
201+
201202 def selector (self ) -> Selector :
202203 """Returns the current selector."""
203204 with self ._lock :
@@ -211,25 +212,13 @@ def _on_memory_store_active(self) -> None:
211212 """
212213 pass
213214
214- def _stage_persist_full (self , collections : Collections , persist : bool ) -> Optional [Collections ]:
215- """
216- Persist a full data set. Subclasses supply the persist step.
217-
218- A subclass either writes the data synchronously and returns None, or
219- returns the collections for the caller to persist afterwards. The
220- subclass owns the decision of whether to persist at all.
221- """
222- raise NotImplementedError
223-
224- def _stage_persist_delta (self , collections : Collections , persist : bool ) -> Optional [Collections ]:
215+ def _should_persist (self ) -> bool :
225216 """
226- Persist a delta update. Subclasses supply the persist step.
227-
228- A subclass either writes the data synchronously and returns None, or
229- returns the collections for the caller to persist afterwards. The
230- subclass owns the decision of whether to persist at all.
217+ Returns whether the current data should be written to the persistent
218+ store. The base engine has no persistent store, so it never persists;
219+ subclasses override this based on their store.
231220 """
232- raise NotImplementedError
221+ return False
233222
234223 def _set_basis (
235224 self , collections : Collections , selector : Selector , persist : bool
@@ -243,8 +232,10 @@ def _set_basis(
243232 persist: Whether to persist the data to the persistent store
244233
245234 Returns:
246- The collections the subclass deferred for later persistence, or None .
235+ The collections to persist, or None if there is nothing to persist .
247236 """
237+ self ._persist = persist
238+
248239 # Take snapshot for change detection if we have flag listeners
249240 old_data : Optional [Collections ] = None
250241 if self ._flag_change_listeners .has_listeners ():
@@ -266,20 +257,17 @@ def _set_basis(
266257 self ._active_store = self ._memory_store
267258
268259 # In-memory store is now authoritative. The subclass reacts here (e.g. by
269- # disabling the persistent-store cache) before the persist step below .
260+ # disabling the persistent-store cache) before the caller persists .
270261 self ._on_memory_store_active ()
271262
272- # Persist through the subclass hook
273- pending = self ._stage_persist_full (collections , persist )
274-
275263 # Send change events if we had listeners
276264 if old_data is not None :
277265 affected_items = self ._compute_changed_items_for_full_data_set (
278266 old_data , collections
279267 )
280268 self ._send_change_events (affected_items )
281269
282- return pending
270+ return collections if self . _should_persist () else None
283271
284272 def _apply_delta (
285273 self , collections : Collections , selector : Selector , persist : bool
@@ -293,8 +281,10 @@ def _apply_delta(
293281 persist: Whether to persist the changes to the persistent store
294282
295283 Returns:
296- The collections the subclass deferred for later persistence, or None .
284+ The collections to persist, or None if there is nothing to persist .
297285 """
286+ self ._persist = persist
287+
298288 ok = self ._memory_store .apply_delta (collections )
299289 if ok is False :
300290 return None
@@ -317,13 +307,11 @@ def _apply_delta(
317307 # Update state
318308 self ._selector = selector if selector is not None else Selector .no_selector ()
319309
320- pending = self ._stage_persist_delta (collections , persist )
321-
322310 # Send change events
323311 if affected_items :
324312 self ._send_change_events (affected_items )
325313
326- return pending
314+ return collections if self . _should_persist () else None
327315
328316 def _changes_to_store_data (self , changes : List [Change ]) -> Collections :
329317 """
@@ -424,9 +412,6 @@ def __init__(
424412 self ._persistent_store_status_provider : Optional [DataStoreStatusProvider ] = None
425413 self ._persistent_store_writable = False
426414
427- # True if the data in the memory store may be persisted to the persistent store
428- self ._persist = False
429-
430415 def with_persistence (
431416 self ,
432417 persistent_store : FeatureStore ,
@@ -456,27 +441,44 @@ def with_persistence(
456441
457442 def apply (self , change_set : ChangeSet , persist : bool ) -> None :
458443 """
459- Apply a changeset to the store.
444+ Apply a changeset to the in-memory store and, if configured, the
445+ persistent store.
460446
461447 Args:
462448 change_set: The changeset to apply
463449 persist: Whether the changes should be persisted to the persistent store
464450 """
465451 collections = self ._changes_to_store_data (change_set .changes )
466452
453+ pending : Optional [Collections ] = None
454+ is_full = False
455+
467456 with self ._lock :
468457 try :
469458 if change_set .intent_code == IntentCode .TRANSFER_FULL :
470- self ._set_basis (collections , change_set .selector , persist )
459+ pending = self ._set_basis (collections , change_set .selector , persist )
460+ is_full = True
471461 elif change_set .intent_code == IntentCode .TRANSFER_CHANGES :
472- self ._apply_delta (collections , change_set .selector , persist )
462+ pending = self ._apply_delta (collections , change_set .selector , persist )
473463 elif change_set .intent_code == IntentCode .TRANSFER_NONE :
474464 # No-op, no changes to apply
475465 return
476466
477467 # Notify changeset listeners
478468 self ._change_set_listeners .notify (change_set )
479469
470+ # Persist synchronously, inline under the lock
471+ if pending is not None :
472+ store = self ._persistent_store
473+ assert store is not None
474+ if is_full :
475+ store .init (pending )
476+ else :
477+ for kind in pending :
478+ kind_data = pending [kind ]
479+ for key in kind_data :
480+ store .upsert (kind , kind_data [key ])
481+
480482 except Exception as e :
481483 # Log error but don't re-raise - matches Go behavior
482484 log .error ("Store: couldn't apply changeset: %s" , str (e ))
@@ -502,27 +504,6 @@ def _on_memory_store_active(self) -> None:
502504 except Exception as e :
503505 log .warning ("Failed to disable persistent store cache: %s" , e )
504506
505- def _stage_persist_full (self , collections : Collections , persist : bool ) -> Optional [Collections ]:
506- self ._persist = persist
507- if not self ._should_persist ():
508- return None
509- store = self ._persistent_store
510- assert store is not None
511- store .init (collections )
512- return None
513-
514- def _stage_persist_delta (self , collections : Collections , persist : bool ) -> Optional [Collections ]:
515- self ._persist = persist
516- if not self ._should_persist ():
517- return None
518- store = self ._persistent_store
519- assert store is not None
520- for kind in collections :
521- kind_data = collections [kind ]
522- for key in kind_data :
523- store .upsert (kind , kind_data [key ])
524- return None
525-
526507 def commit (self ) -> Optional [Exception ]:
527508 """
528509 Commit persists the data in the memory store to the persistent store, if configured.
0 commit comments