Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Price guardian #1716

Open
wants to merge 2 commits into
base: stable
Choose a base branch
from
Open

Conversation

christopherlam
Copy link
Contributor

Rewrite for #1403

Adds mechanism for guarding SCM objects referring to price. gnc:price-ref will guard the price. As long as the SCM object is alive, the price ref will be increased; when gc runs, the guardian will appropriately unref prices.

If a report which accesses price is run, and gc hasn't triggered yet, then the price ref would not be decremented yet. Hence the call to scm_gc() after running a report.

@christopherlam christopherlam marked this pull request as ready for review July 21, 2023 06:42
gnucash/report/gnc-report.cpp Show resolved Hide resolved
Comment on lines -473 to 474
(if price (gnc-price-unref price))
(set! pricing-txn trans)
(set! price trans-price)
Copy link
Member

Choose a reason for hiding this comment

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

This bugs me. Does (gnc:price-ref price) put price or *price (in C's meaning, Guile having no way to express it) on the guardian's list? If it's *price then no problem; otherwise this will leak the original *price and if trans-price will get an extra unref.

Copy link
Contributor Author

@christopherlam christopherlam Jul 28, 2023

Choose a reason for hiding this comment

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

well, the reports shouldn't generally call gnc-price-ref/unref, and shuould leave it to the guardians. I don't know whether it calls price or *price.

Copy link
Member

Choose a reason for hiding this comment

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

They don't generally, only the two portfolio reports do. Nothing in Scheme except test-engine-extras.scm writes to price objects, so one option would be to copy the few things that reports need from prices (currency, commodity, time, and value) and not swig any of the price API.

Do you understand the problem with (set! price trans-price) if the guardian is using the addresses of price and trans-price instead of the addresses that they point to (e.g. *price)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you understand the problem with (set! price trans-price) if the guardian is using the addresses of price and trans-price instead of the addresses that they point to (e.g. *price)?

Not quite.

Copy link
Member

Choose a reason for hiding this comment

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

There are two GncPrice pointers, p1 and p2. price is initialized to p1 and trans-price to p2. price-guardian is called on price and trans-price when each is initialized, then (set! price trans-price) happens and both price and trans-price equal p2.

If (price-guardian price) causes the guardian to keep track of p1 (what I called the '*price' scenario) then after the set! call it's garbage-collected and returned and unreffed by reclaim-prices. Later when both price and trans-price go out of scope (maybe at the end of the report render)p2 is garbage collected and can be unreffed by reclaim-prices. All good.

But if (price-guardian price) keeps a reference to price (the 'price' scenario) then it's not guarding p1. It doesn't return anything until price or trans-price go out of scope, and when they do it returns p2 for each one, so p1 leaks and p2 gets unreffed twice. Not good.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see slightly and don't know the answer.

Copy link
Member

Choose a reason for hiding this comment

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

Then you need to find out. If it's the latter case then the set! has to go, it's a potential use-after-free crasher.

Alternative: Remove all of the price refs and unrefs, no guardians. I don't think any of these calculations will be interrupted by a status bar update, and even if they can the user would have to somehow queue a price-delete to run during an idle for a price to become invalid while the report is loading. Pretty darn unlikely.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alternative: Remove all of the price refs and unrefs, no guardians. I don't think any of these calculations will be interrupted by a status bar update, and even if they can the user would have to somehow queue a price-delete to run during an idle for a price to become invalid while the report is loading. Pretty darn unlikely.

So, #1403 resurrected.

Copy link
Member

Choose a reason for hiding this comment

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

OK, probably safer than this. But copying it into a scheme record would be safer still.

increases refcount for price, and adds a gc hook to the price SCM
object. when the SCM object is gc, it's appropriately unreffed.
@christopherlam
Copy link
Contributor Author

I had another idea to move this guardian to engine.i and disallow gnc_price_ref/unref from being swigged.

@richardcohen
Copy link
Contributor

@christopherlam did you try using swig %refobject ?
https://www.swig.org/Doc3.0/SWIGPlus.html#SWIGPlus_ref_unref

@christopherlam
Copy link
Contributor Author

@christopherlam did you try using swig %refobject ?
https://www.swig.org/Doc3.0/SWIGPlus.html#SWIGPlus_ref_unref

Good find... unfortunately I have no idea how this works.

@jralls
Copy link
Member

jralls commented Aug 1, 2023

swig %refobject

It's for "legacy C++", meaning before C++11. Modern C++ uses std::shared_ptr instead of hand-rolling ref counting. It also appears to require ref and unref to be member functions, so it will work only with a C++ class, and GncPrice is a GObject class. That aside a check of the source code suggests that it isn't even fully implemented.

Having reread again the Guile Memory Management docs I'm pretty sure that guile's GC only knows about guile objects (the 'price' model). So guardians are a fine way to handle lifetime but the guarded variable can't be mutated: No set!.

@jralls
Copy link
Member

jralls commented Sep 10, 2023

I had another idea to move this guardian to engine.i and disallow gnc_price_ref/unref from being swigged.

It's nicer because it hides the guardians from the report code, but it still has the reference problem.

Alternative: Remove all of the price refs and unrefs, no guardians. I don't think any of these calculations will be interrupted by a status bar update, and even if they can the user would have to somehow queue a price-delete to run during an idle for a price to become invalid while the report is loading. Pretty darn unlikely.

So, #1403 resurrected.

No, #1403 wanted to remove the memory management from gnc-pricedb.c, a terrible idea. I'm just suggesting to remove it from the two portfolio reports. Copying the price info into Scheme would reduce the risk still further; doing so in wrapped C code that refs the gnc_price, makes the copy, then unrefs the gnc_price would be safest.

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.

3 participants