Repository navigation
Set an attribute only while its column is in the store - #361
Merged
Merged
Conversation
Element.setAttribute and removeAttribute, including the variants with a timestamp or an interval, checked the column before writing without the table lock, so a concurrent removeColumn made the write fail with an ArrayIndexOutOfBoundsException, the index update fail with a NullPointerException, or left the value in the cleared slot, where a column added later read a value of the wrong type. The value is now written only if the column is still in the store, checked under the attributes lock, and removeColumn removes the column before clearing the values. Otherwise the setter throws the IllegalArgumentException of a removed column. An index update finds no index for a column added or removed concurrently, and removing a column that is not the registered one fails before anything is changed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
removeColumn(String) resolved the key before taking the graph and table locks, so a column removed and added again in between could be removed instead of the one named. Also adds a test for removing a time attribute of an element whose attributes are shorter than the column index. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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.
Problem
A setter racing
removeColumnfailed in three ways:Element.setAttributechecked the column and then wrote the value into the slot of the column without a lock, so a column removed in between made the write fail with anArrayIndexOutOfBoundsException(the store id of the column became -1), or the index update fail with aNullPointerException(IndexImpl.setfound no index for the column).removeColumnhad cleared it. A column added later in the same slot then read a value of the wrong type, andaddColumnfailed with aClassCastException.setAttribute(column, value, time)andremoveAttribute(column, time).With a stress test of 10,000 remove and add rounds against one setter thread, master threw 333
NullPointerExceptions, 253ArrayIndexOutOfBoundsExceptions and 247ClassCastExceptions.Fix
No lock is added to the setters (a table lock in the setters deadlocks with iterating an index):
AttributesImplwrites the value only if the column is still registered in the store (columns[index] == column), checked and written in one hold of the attributes monitor of the element. This coverssetAttribute,removeAttributeand their time variants. Otherwise the setter throws theIllegalArgumentExceptionthat a call on an already removed column throws.ColumnStore.removeColumnremoves the column from the store before it clears the values. A value is therefore either written while the column is registered and then cleared, or the setter sees the column is gone.removeColumnchecks that the column passed in is the registered one before it changes anything. Removing a stale handle of a column removed earlier used to unregister the column that had been added with the same key.IndexImpl.setskips the update when the column has no index, which happens while a column is added or after it is removed.ColumnStore.removeColumn(String)looks the key up under the graph and table locks, so a column removed and added again in between is not removed instead of the one named.AttributesImpl.removeTimeAttributeno longer fails with anArrayIndexOutOfBoundsExceptionwhen the attributes of the element are shorter than the index of the column.Tests
IndexStoreTest.testSetAttributeWhileRemovingColumnandtestSetTimeAttributeWhileRemovingColumn: a setter thread against 20,000 rounds ofremoveColumnandaddColumnthat reuse the slot with another type. Only theIllegalArgumentExceptionmay leave the setter, and the value in the slot has the type of the last column. Both fail on master.IndexStoreTest.testRemoveTimeAttributeOfColumnAddedAfterNodeCreated: removing a time attribute of an element whose attributes are shorter than the column index returns null instead of throwing.ColumnStoreTest.testRemoveColumnTwiceKeepsColumnWithSameKey: removing a stale handle fails with anIllegalArgumentExceptionand leaves the column added later with the same key working.Setting an attribute costs the same as before within noise (about 330 to 350 ns for an indexed Integer column in the store).
Not covered
removeColumncan have its times released before the setter adds them to the time index.clearAttributes()can throw theIllegalArgumentExceptionif a column is removed while it runs.addNodeand time setters (TimestampMap.toKeysArray) still exists.🤖 Generated with Claude Code