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

Table alloc tracking #267

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open

Table alloc tracking #267

wants to merge 6 commits into from

Conversation

tul
Copy link
Contributor

@tul tul commented Jan 7, 2020

Fixes #250

Changes proposed in this pull request:

  • LStates can now optionally limit how many LTables and how many LTable keys are set
  • This provides basic memory quotas for LStates as LTable growth is the most common way for an LState to grow in memory use
  • Defaults to not being tracked
  • When enabled - has almost negligible speed and memory impact

allows tracking the max number of tables and table keys
used by an LState, so that its memory usage can be
roughly tracked and limited.
@coveralls
Copy link

coveralls commented Jan 7, 2020

Coverage Status

Coverage increased (+1.2%) to 89.286% when pulling 0c66945 on tul:table_alloc_tracking into ab39c60 on yuin:master.

@tul
Copy link
Contributor Author

tul commented Jan 31, 2020

@yuin what do you think? Happy to hear your thoughts.

@novabyte
Copy link

novabyte commented Mar 3, 2020

@tul This is fantastic work. Would love to see this included in gopher-lua. What do you think @yuin?

@yuin
Copy link
Owner

yuin commented Mar 12, 2020

Awesome work and great contribution @tul! I'm very sorry about this.

I'm very busy with my work until end of March. I will look this PR once things settle down at work.

// is because the capacity of a slice is automatically managed by the go runtime and is free to change between versions
// of go. This would mean Gopher Lua could behave differently between different version of go, which is not desirable.
// We could switch to using capacity if we moved away from using the `append` built in for the table slice.
type LTableAllocInfo struct {
Copy link
Owner

Choose a reason for hiding this comment

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

LTableAllocInfo fields are private. It seems useless for users.

// CheckQuota checks if this table's alloc info's quotas have been exceeded and if so invoke's the LState's quota
// exceeded callback. CheckQuota is called automatically by vm operation which modify tables, so only needs to be
// invoked directly if you are writing code which has directly added keys to a table using the Raw set methods.
func (tb *LTable) CheckQuota(L *LState) {
Copy link
Owner

Choose a reason for hiding this comment

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

I feel uncomfortable that the function takes *LState . This function should be implemented as a method of LState .

@@ -1129,6 +1138,8 @@ func (ls *LState) setField(obj LValue, key LValue, value LValue) {
ls.RaiseError("attempt to index a non-table object(%v) with key '%s'", curobj.Type().String(), key.String())
}
ls.RawSet(tb, key, value)
// potentially a new key was created, so check the quota
tb.CheckQuota(ls)
Copy link
Owner

Choose a reason for hiding this comment

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

This statement should be wrapped by if to avoid needless function call that relatively high cost.

if ls.tblAllocInfo != nil {
  tb.CheckQuota(ls)
}

@tul
Copy link
Contributor Author

tul commented Sep 11, 2020

@yuin Apologies, I have only just seen your comments. I will review them and respond next time I'm in the code area.

I also have a further update to this PR to optionally force a GC when a quota is exceeded, as I found that Go's lazy GC would sometimes lead to Lua scripts with a high churn of LTables exceeding quota just because the GC hadn't freed its unreferenced LTables yet.

@hbagdi
Copy link

hbagdi commented Jul 14, 2021

@tul Thanks for the patch. Any update on getting this over the line?

@tul
Copy link
Contributor Author

tul commented Jul 18, 2021

To be honest after having used this in production for a while I’m considering alternative approaches.

The problems I’ve had with this approach:

  1. scripts which churn a lot of memory quickly, eg during initialisation they create a lot of garbage, can be incorrectly killed for being over quota (they are only over quota due to GC not yet having fired)
  2. Putting in logic to fire the GC manually when quotas are exceeded (not in this patch but I have locally) hit performance very hard. It may be possible to optimise it by coalescing and scheduling GCs, but I have doubts.
  3. When a script does exceed its quota, often due to a leak, this PR offers no insights into “where” the unreleased memory is being retained.

So in short, I’m thinking of alternative approaches, possibly a combination of light weight finaliser based counting like this, combined with registry analysis to determine where tables are retained (essentially allowing a per LState “retention graph” to be produced).

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.

LState memory tracking feature
5 participants