Serialise BlockSchematicStructure.Unpack's lazy initialisation - #53
Open
Zaldaryon wants to merge 1 commit into
Open
Serialise BlockSchematicStructure.Unpack's lazy initialisation#53Zaldaryon wants to merge 1 commit into
Zaldaryon wants to merge 1 commit into
Conversation
Unpack() lazily initialises the schematic on first use, with an unsynchronised check-then-act. The same BlockSchematicStructure instance is shared by every worldgen thread that places that structure, so with MaxWorldgenThreads above 1 two threads can both see blocksByPos null and both run the initialisation. The worse case is subtler. Init assigns blocksByPos partway through its own work, before LoadMetaInformationAndValidate has run, so a second thread can find blocksByPos non-null, return from Unpack believing the schematic is ready, and read fields that are not populated yet. PathwayStarts is one of those, and reading it early throws. Both entry points now take the same per-instance lock. There is deliberately no lock-free fast path, because blocksByPos being non-null does not mean unpacking has finished, so a caller cannot safely skip the lock on that basis.
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Unpack()initialises the schematic lazily on first use, with an unsynchronised check-then-act:A
BlockSchematicStructureinstance is shared by every worldgen thread that places that structure, so withMaxWorldgenThreadsabove 1 two threads can both seeblocksByPosnull and both run the initialisation.The more damaging case is quieter.
InitassignsblocksByPospartway through its own work, well beforeLoadMetaInformationAndValidatehas run. So a second thread can findblocksByPosnon-null, return fromUnpackbelieving the schematic is ready, and then read fields that nothing has populated yet.PathwayStartsis one of those, and reading it early throws.This puts both entry points behind one per-instance lock.
There is no lock-free fast path on purpose.
blocksByPosbeing non-null does not mean unpacking has finished, so a caller cannot use it to decide the lock is unnecessary. Since the lock is per instance and only contended on the first placements of a given schematic, the cost after warm-up is an uncontended monitor acquire.Found while running worldgen with several threads. Related but separate: anegostudios/VintageStory-Issues#9871.