-
-
Notifications
You must be signed in to change notification settings - Fork 418
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve cycle detector handling of short-lived actors
We have a few example programs that create a large number of short-lived actors that can exhibit runaway memory growth. The changes in this commit greatly reduce the memory growth or potentially reduce it to be stable. The primary change here is to use some static analysis at compilation time to determine if an actor is "an orphan". That is, upon creation, has no actors (including it's creator) with references to it. Prior to this change, examples such as the one from issue #1007 (listed later) would be unable to be collected due to an edge-case in the cycle detector and the runtime garbage collection algorithms. Issue #1007 was opened with the following code having explosive memory growth: ```pony primitive N fun apply(): U64 => 2_000_000_000 actor Test new create(n: U64) => if n == 0 then return end Test(n - 1) actor Main new create(env: Env) => Test(N()) ``` There are three additional examples that were partially addressed by #3647. #1007 wasn't addressed by #3647 because, the key to the previous fix that Dipin and I came up with was that when done running its behaviors, an actor can see if it has no additional messages AND no references to itself and can then tell the cycle detector to skip parts of the CD protocol and garbage collect sooner. The above change allows for the cycle detector to keep up with many cases of generating large amounts of "orphaned" actors. The example above wasn't addressed by #3647 because of an implementation detail in the ORCA garbage collection protocol; at the time that an instance of Test is done running its create behavior, it doesn't have a reference count of 0. It has a reference count of 256. Not because there are 256 references but because, when an actor is created puts a "fake value" in the rc value such that an actor isn't gc'd prematurely. The value will be set to the correct value once the actor that created it is first traced and will be subsequently updated correctly per ORCA going forward. However, at the time that an instance of Test is finished running its create, that information isn't available. It would be incorrect to say "if rc is 256, I'm blocked and you can gc me". 256 is a perfectly reasonable value for an rc to be in normal usage. This isn't a problem with the changes in this PR as the compiler detects that each instance of Test will be an orphan and immediately sets its rc to 0. This allows it to be garbage collected based on the changed in #3647 as soon as the instance's message queue is empty. Any changes in the future to address lingering issues with creating large numbers of orphaned actors should also be tested with the following two examples. Example 2 features reasonably stable memory usage that I have seen from time-to-time, increase rapidly. It should be noted that such an increase is rather infrequent but suggests there are additional problems in the cycle-detector. I suspect said problem is a periodic burst of additional messages to the cycle-detector from actors that can be garbage collected, but I haven't investigated further. ```pony ctor Main new create(e: Env) => Spawner.run() actor Spawner var _living_children: U64 = 0 new create() => None be run() => _living_children = _living_children + 1 Spawnee(this).run() be collect() => _living_children = _living_children - 1 run() actor Spawnee let _parent: Spawner new create(parent: Spawner) => _parent = parent be run() => _parent.collect() ``` Example 3 has stable memory growth and given that it won't result in any messages being sent to the cycle detector as we have determined at compile-time that the Foo actor instances are orphaned. ```pony actor Main var n: U64 = 2_000_000_000 new create(e: Env) => run() be run() => while(n >= 0 ) do Foo(n) n = n - 1 if ((n % 1_000) == 0) then run() break end end actor Foo new create(n: U64) => if ((n % 1_000_000) == 0) then @printf[I32]("%ld\n".cstring(), n) end None ``` Example 4 has the same characteristics as example 3 with the code as of this commit. However, it did exhibit different behavior prior to this commit being fully completed and appears to be a good test candidate for any future changes. ```pony actor Main var n: U64 = 2_000_000_000 new create(e: Env) => run() be run() => while(n >= 0 ) do Foo(n) n = n - 1 if ((n % 1_000_000) == 0) then @printf[I32]("%ld\n".cstring(), n) end if ((n % 1_000) == 0) then run() break end end actor Foo new create(n: U64) => None ``` Closes #1007
- Loading branch information
1 parent
f920092
commit d17cac2
Showing
10 changed files
with
57 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters