-
Notifications
You must be signed in to change notification settings - Fork 858
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
SlotMap.compute can lead to deadlock in ThreadSafeSlotMapContainer #1746
SlotMap.compute can lead to deadlock in ThreadSafeSlotMapContainer #1746
Conversation
…case of Dead Lock that can occure when new SlotMap.compute method is used with ThreadSafeSlotMapContainer
Closing to prevent CI deadlocks |
Cancelled |
@p-bakker sorry i guess it is too late now |
This is a great find -- thanks, and I'm going to spend a bit of time understanding it since I introduced a deadlock! This is the first time I've seen the new "var" keyword in a PR. (I know, it's not really a "keyword.") It was introduced in Java 10 so it's fair game for us to use AFAIK! |
Not correct, because core-js is still on java 8 i had to 'backport' already some var's after the merges. For me it will be fine to live without var - i think it makes our complicated code a bit more readable at most places. |
boolean isSameGetterFunction(Object function) { | ||
if (function == Scriptable.NOT_FOUND) { | ||
return true; | ||
} else if (getter == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a matter of taste - but there is no else needed because of the return
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed.
return slot; | ||
} | ||
|
||
private ScriptableObject getBuiltInDescriptor(String name) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe getBuiltInDataDescriptor is a better name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, and changed.
There were already three I actually find |
That's most likely me, introduced as part of #1577 , so it's fairly recent. I personally also find |
Doing some more testing on this, if I set Following suites are the problem:
They all have a variation of below Object.defineProperty(Object.prototype, "1", {
get: function() {
return 6.99;
},
configurable: true
}); which passes
We're working on fixing this too, I mostly wanted to share the new details. |
Having said that, I do worry that we don't catch those Would running a CI build with |
…fineProperty to avoid deadlocks, that happen when current instance of ScriptableObject is used as scope inside slotMap.compute lambda.
OK -- let me look at this again, and resolve three things: First -- I agree -- "var" is fine! We moved to Java 11 minimum in this release so there should be no problems (and TBH I don't know how "var" shows up in the bytecode so it might matter even less). Second -- we should update the docs for SlotMap to indicate that the lambda in "compute" must not lock the slot map. Third -- there is another way to resolve this, because I assume that the "deadlock" you mention is a problem with the non-recursive nature of the lock. We could replace all that "stamped lock" stuff in ThreadSafeSlotMapContainer with simple "synchronized" blocks, which are recursive and which optimize out pretty well when there's no contention anyway. But if you're good with this change, I'll look at it and it's also fine. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this! There are changes in some fundamental places here so I looked at the code coverage of the changed code (you can get this with the "testCodeCoverageReport" gradle task, instructions in the README) and I see a few places where stuff that's changed in this PR isn't covered.
Can you see if you can add some test cases to your new tests (or old ones if you'd like) so that we hit those spots in testing?
If it's onerous I'm happy to hear about it, but I'm hoping that it's not, and a bit more coverage would help in this case. Thanks!
// it's dangerous to use `this` as scope inside slotMap.compute. | ||
// It can cause deadlock when ThreadSafeSlotMapContainer is used | ||
|
||
return replaceExistingLambdaSlot(cx, name, existing, newSlot); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just looking at code coverage, since you're changing some fundamental stuff here, and I see that line 1758 doesn't get covered. Do you think that you could devise a test case for that and add it to your new test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing this out, 474c3ba should take care of it.
|
||
if (ScriptRuntime.isSymbol(id)) { | ||
if (id instanceof SymbolKey) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, looking at coverage, nothing from line 957 to 961 seems to get covered either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is a little more convoluted: as of right now, I think it's impossible for this code path to be executed, thus it will be challenging to test it. This is because it's only called from IdScriptableObject::defineOwnProperty
( here and here) and before the call, we've already established that key
must be a CharSequence, so it can't a Symbol. So maybe the solutions is to remove this branch?
Having said that, this doesn't feel right and perhaps it's an issue with current implementation of IdScriptableObject::defineOwnProperty
, since it's only handling String (or CharSequence) keys and essentially delegating Symbol key'ed properties to it's base class (ScriptableObject). It's very consistent with how getOwnPropertyDescriptor
implementation, which does try to find built in descriptor using both Strings and Symbols (here ). I believe this currently works mostly, because we always check base class for property descriptor first, but it's a little confusing why we need that symbol lookup.
One could also imagine, someday in the future, someone creating a new built-in NativeX
class extending IdScriptableObject
that defines a Symbol keyed property, that's shouldn't be redefined ( i.e. configurable: false
) and current implementation of IdScriptableObject::defineOwnProperty
would not check it ( only Strings are handled) and allow base class implementation to override it.
I appreciate it's a little theoretical, but wanted to share this and ask for opinions on whether we should address this? Also if addressing this deserves a separate PR or should I add it here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, thanks -- I suppose one way we could deal with this could be to assert, and in the future if someone wants to implement a subclass that needs it, we'd fix it -- although in many cases we are trying to get rid of IdScriptableObject as it doesn't yield the performance benefit it once did. If you're not comfortable with that, however, I'd also be OK leaving this as it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally wouldn't mind leaving it as is for now, I don't see an obvious place to assert that wouldn't break existing functionality or be completely redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, then I'm satisfied, thanks!
…cover that code path
New SlotMap compute method introduced in b7ece52 can lead to a Deadlock during re-definition of existing object properties when used in combination with
ThreadSafeSlotMapContainer
.The problem occurs, because
ThreadSafeSlotMapContainer
obtains and holds a write lock for the entire duration of method execution, includingSlotComputer
lambda logic and in certain conditions (details below)ScriptableObject.defineOwnProperty
call togetPropertyDescriptor
will attempt to obtain a read lock on the same slotMap instance from insideSlotComputer
lambda body, as marked on below code snippet :Conditions necessary for this problem to occur:
ThreadSafeSlotMapContainer
, controlled byContext.FEATURE_THREAD_SAFE_OBJECTS
which is not ON by defaultexisting.getPropertyDescriptor
if existing != null.existing.getPropertyDescriptor(cx, this)
is called during property redefinition, it will be used as the "global scope" when Property Descriptor object is initialised.this
object is passed toScriptRuntime.getBuiltinPrototype
as scopethis
object- which requires read lock on the same instance of
ThreadSafeSlotMapContainer
thatslotMap.compute
is already locking.This PR adds 2 test cases reproducing the same issue, both with
Context.FEATURE_THREAD_SAFE_OBJECTS
switched ON to force use ofThreadSafeSlotMapContainer
:DeadlockReproTest
, the most minimal repro we could come up withredefineGetterProperty
test, showing that just changing SlotMapContainer causes the deadlock to occur.