Skip to content

Pass timestamps from Timeseries to ModifiablePersistenceServices - #5733

Open
mjagdis wants to merge 3 commits into
openhab:mainfrom
mjagdis:ts-persisttime
Open

Pass timestamps from Timeseries to ModifiablePersistenceServices#5733
mjagdis wants to merge 3 commits into
openhab:mainfrom
mjagdis:ts-persisttime

Conversation

@mjagdis

@mjagdis mjagdis commented Jul 26, 2026

Copy link
Copy Markdown

Pass the timestamps given in Timeseries through to ModifiablePersistenceServices rather than just letting them pick their own and independent idea of "now".

N.B. It would be nice to have correct timestamps in QueryablePersistenceServices too but they don't have the extended store method and since it's an interface hierarchy rather than a class hierarchy fixing that might take a bit of coordination between core and addons. I don't think it's desperately urgent for the queryables...

Signed-off-by: Mike Jagdis <mjagdis@eris-associates.co.uk>
@mjagdis
mjagdis requested a review from a team as a code owner July 26, 2026 22:34
@mherwege

Copy link
Copy Markdown
Contributor

While this could be a quick fix, why not make it more general and open the way to improve the addons from here. I would suggest the following:

  • add overrides to the store method in the persistenceService interface with an extra argument for now, that defaults to calling the method, without the now argument. Clearly explain the purpose of this in the method documentation.
  • persistence services can implement this enhanced method if they can support it, but some services may not be able to (I don't think RRD4J would be able to persist that way, the timestamp is set in the library if I am not mistaken). These are then improvements that can be done without breaking anything.
  • Override the extra method in the ModifiablePersistenceService implementation to call the method from ModifiablePersistenceService with the Item state.
  • You can then avoid the special case for ModifiablePeristenceService in PersistenceManagerImpl and just call this enhanced method.

Signed-off-by: Mike Jagdis <mjagdis@eris-associates.co.uk>
@mjagdis

mjagdis commented Jul 27, 2026

Copy link
Copy Markdown
Author

How about this?

long startTime = System.nanoTime();
itemConfig.filters().forEach(filter -> filter.persisted(item));
persistenceService.store(item, getAlias(item));
persistenceService.store(item, now, item.getState(), getAlias(item));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an item's state changes while iterating over the items, the date will be wrong. You should only apply this fix when restoring forecasts.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try this then. There always is a risk that the state can change under us because GenericItem doesn't seem to have any atomicity guarantees but this should be no worse than previously. Possibly slightly better since now the core ensures now and getState are always next to each other whereas before it depended how the service implemented store.

Signed-off-by: Mike Jagdis <mjagdis@eris-associates.co.uk>
itemConfig.filters().forEach(filter -> filter.persisted(item));
container.getPersistenceService().store(item, container.getAlias(item));
PersistenceService persistenceService = container.getPersistenceService();
persistenceService.store(item, Objects.requireNonNullElse(item.getLastStateUpdate(), now),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lastStateUpdate is not guaranteed to be updated when this is called. It will likely be as it runs in a separate thread, but applyState in GenericItem explicitly only updates it after the notifyListeners call. So I am not sure this is always valid.

void store(Item item);
@Deprecated
default void store(Item item) {
store(item, ZonedDateTime.now(), item.getState(), null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make these NOOP, or you end up in a circular reference if nothing is implemented.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the persistence API and persistence manager flow so that a concrete timestamp/state can be forwarded into persistence services (instead of each service deriving its own “now”), improving correctness for backfilled/time-series driven updates.

Changes:

  • Extend PersistenceService with timestamp/state-aware store(...) overloads and migrate call sites to use them.
  • Remove duplicate historic store(...) declarations from ModifiablePersistenceService (now inherited from PersistenceService).
  • Update persistence manager tests to verify the new store(...) method signature is used.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
bundles/org.openhab.core.persistence/src/test/java/org/openhab/core/persistence/internal/PersistenceManagerTest.java Updates Mockito verifications to expect timestamp/state-aware store(...) calls.
bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceService.java Introduces timestamp/state-aware store(...) overloads and deprecates the legacy methods.
bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/ModifiablePersistenceService.java Removes now-redundant historic store(...) method declarations.
bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java Switches persistence writes to pass explicit timestamps/states through to PersistenceService.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +135 to +138
@Deprecated
default void store(Item item, @Nullable String alias) {
store(item, ZonedDateTime.now(), item.getState(), alias);
}
Comment on lines +96 to +99
* @param item the data to be stored
* @param date the date of the record
* @param state the state to be recorded
*/

// Check if other persistence services got updated
verify(persistenceServiceMock).store(TEST_ITEM, null);
verify(persistenceServiceMock).store(eq(TEST_ITEM), any(ZonedDateTime.class), any(State.class), eq(null));

@wborn wborn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI reviewed the current PR HEAD and the existing review discussion.

The general approach of adding timestamp/state-aware store(...) overloads to PersistenceService, while allowing existing persistence services to fall back to their current implementation, looks reasonable.

There are still some unresolved issues that should be addressed before merging:

  • As already pointed out in the existing review, item.getLastStateUpdate() is not reliable for normal state callbacks because GenericItem updates it after notifying listeners. The timestamp from the restored time-series entry should only be propagated along paths where that timestamp is actually known.
  • The current default store(...) methods can recurse indefinitely when an implementation overrides neither side of the compatibility bridge.
  • The time-series test should verify the actual timestamp and state forwarded to the other persistence service instead of accepting any values.
  • The missing @param alias Javadoc should be added.

There is also one related API migration that should be included here: PersistenceExtensions.internalPersist(Item, ...) still calls the now-deprecated store(item, alias) overload. Since this PR establishes the timestamp/state overload as the replacement, core should use the new API itself. This is particularly important if the deprecated defaults are made no-ops to resolve the recursion issue.

Another maintainer should still review the PR after the AI review and the remaining issues have been addressed.

*/
void store(Item item, @Nullable String alias);
@Deprecated
default void store(Item item, @Nullable String alias) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PersistenceExtensions.internalPersist(Item, ...) still calls this overload. Since this PR deprecates it in favor of the timestamp/state-aware method, can we migrate that core caller as part of this change as well?

This becomes particularly important if these deprecated defaults are made no-ops to resolve the recursion discussed above: a persistence service implementing only the new API would otherwise silently ignore persist(item) calls. Using the new overload there with ZonedDateTime.now() and item.getState() should preserve the existing semantics while keeping the compatibility delegation one-directional.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants