Skip to content

Conversation

Monarda
Copy link

@Monarda Monarda commented May 27, 2025

This is an attempt to simplify PR #155 so that it's simply about adding new methods (post, open, and close) to the Handler and SharedPV classes in p4p.server.raw.

example\persist.py shows what can be achieved with the new open and post methods. The example uses an SQLite database to persist the values of channels to a (database) file. When the program is restarted these values are read back and automatically restored to their original channels with their original timestamps. persist.py uses the post functionality to update the values of the PVs, automatically updating their timestamps, and persisting the changed values. It uses the open functionality to restore the values on restart. The use of the incrementing value shows a scenario in which it is more convenient to do this in the handler with the new methods (using the combination of open and post) and would not be as simple with existing methods such as onFirstConnect.

@Monarda Monarda force-pushed the new_handler_methods branch from 025a119 to 687a21b Compare May 27, 2025 21:09
Copy link
Member

@mdavidsaver mdavidsaver left a comment

Choose a reason for hiding this comment

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

Thank you @Monarda , this PR is certainly smaller as I asked.

Comment on lines 78 to 82
def post(self, pv: SharedPV, value: Value, **_kws):
"""Update timestamp of the PV and store its new value."""
self._update_timestamp(value)

self._upsert(value)
Copy link
Member

Choose a reason for hiding this comment

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

Do I understand correctly that you mean to propagate every update of this SharedPV directly to an sqlite database file? Have you done any performance measurements? (on NFS...?)

fyi. the autosave module design avoids this potentially large performance hit by decoupling record processing from .sav file I/O with a timer and separate worker thread. create_monitor_set() configures a minimum time to hold off between saves to coalesce PVs with frequent changes. The logic being to note (by monitor) when one PV changes, wait for the hold-off time, then write out all PVs which have changed since the last write.

This lets an IOC gracefully ride through NFS server outages.

I know this is only an example, and setting the time stamp is reasonable enough, but the "persist" part scares me.

Copy link
Author

Choose a reason for hiding this comment

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

I haven't done any performance testing on this. It's not been used outside of this example. It's not intended as any kind of replacement for autosave. It would need exactly the kind of work you suggest to be able to be used for such a purpose.

I've added a note to the comments at the top of the persist.py example to make this advice not to use as anything other than an example explicit.

I've also added a new example example/auditor.py which does something similar but ought to be much more obviously flawed. (And I've added the same kind of explicit note to its opening comments.) The auditor example is less useful but does cover a few more Handler methods.

# Guard goes here because we can have handlers that don't inherit from
# the Handler base class
try:
self._handler.open(V, **kws)
Copy link
Member

Choose a reason for hiding this comment

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

At this point in open() and post(), **kws have been digested by self._wrap(). Is there still anything meaningful to be done with them?

Copy link
Author

Choose a reason for hiding this comment

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

I've removed a lot of unnecessary kwargs now. These were leftover from a more complex previous version.

In future I'll probably open a new issue to discuss their use case, but they didn't belong in this PR.


_SharedPV.post(self, V)

def close(self, destroy=False, sync=False, timeout=None):
Copy link
Member

Choose a reason for hiding this comment

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

Where do these extra arguments come in?

Copy link
Author

Choose a reason for hiding this comment

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

I believe they may have been from an earlier version of p4p, but I'm not sure! I've removed sync, and timeout now. I've left destroy.

except AttributeError:
pass

def close(self, pv):
Copy link
Member

Choose a reason for hiding this comment

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

Remove the unused pv argument.

Copy link
Author

Choose a reason for hiding this comment

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

I've removed it now

value = op.value().raw

self._update_timestamp(value)
self._upsert(value, op.account(), op.peer())
Copy link
Author

Choose a reason for hiding this comment

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

Is there a better way to do this? Originally I was using pv.post(op.value()) which is much neater, but discards the information about op.account() and op.peer(). Using op.value().raw feels brittle, and will discard any data apart from the value.

@Monarda
Copy link
Author

Monarda commented Aug 26, 2025

Added some simple unit tests

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.

2 participants